Skip to content
6b3d345
@evanp @kabniel @rfinnie @h4ck3rm1k3 @Fil @Screwtapello
615 lines (472 sloc) 23.7 KB

pump.io API

pump.io API is based on three major technologies:

There are some bits of other things floating around, like:

Finally, the API uses REST-ish principles and follows some of the patterns, but none of the actual requirements, of The Atom Publishing Protocol.

TL;DR

Here's the quick start version of the API:

  • Register a new OAuth client by posting to the client registration endpoint. See : bin/pump-register-app for an example implementation.
  • Use OAuth 1.0 to get an OAuth token for the user. See : bin/pump-authorize for an example implementation.
  • Post to the user's activity outbox feed to create new activities. These can create new content, respond to existing content, or modify the social graph.
  • Read from the user's activity inbox to see stuff that other people have sent to them.

Activity Streams

Activity Streams is a format for representing events or activities in a social network or in collaborative software. It's a JSON format (at least, we only support the JSON format), meaning that on the wire data looks like JavaScript literals.

Activity Streams data includes a few different kinds of things:

  • objects: These represent real or digital objects. Every object has an objectType property; some common object types are "person", "note", "image", "place". There are some standard properties that all object types support, like displayName for an easy-to-use short text name, or content for HTML content representing the object. Some types have unique properties, like a "place", which has a position and an address. Every object has a unique id, which is a URI that identifies that object globally.
  • activities: An activity is something that happened. It's like a sentence; it has an actor, who did the thing, a verb that is what happened, and an object which is what it happened to. Activities can also have some other properties, like a location or a target. Activities also have an id property that uniquely identifies the activity.
  • collections: These are ordered lists of activities.

An example of an Activity Streams activity:

{
    "id": "http://coding.example/api/activity/bwkposthw",
    "actor": {
        "id": "acct:bwk@coding.example",
        "displayName": "Brian Kernighan",
        "objectType": "person",
        "url": "http://coding.example/bwk"
    },
    "verb": "post",
    "object": {
        "id": "http://coding.example/api/note/helloworld",
        "content": "Hello, World!"
        "objectType": "note"
    },
    "published": "1973-01-01T00:00:00"
}

This activity has an id to uniquely identify it, an actor, a verb, and an object. It also has a publication timestamp, published. The actor is a "person" with a name and an id that is an "acct:" URI, as well as an url of a profile page. The object is a "note".

The activity streams specification is long; there are also several extensions that pump.io supports. The Activity Base Schema lists some common object types and verbs.

It's possible to make new object types and new verbs by using full URIs for the objectType or verb property. Unknown object types or verbs will be stored but won't cause side-effects.

Feed basics

Each user account on a pump.io server has two main feeds:

  • An activity outbox at /api/user/<nickname>/feed. This is where the user posts new activities, and where others can read the user's activities.
  • An activity inbox at /api/user/<nickname>/inbox. This is where the user can read posts that were sent to him/her. Remote servers can post activities here to be delivered to the user (see below).

The feeds are collections of activities.

Creating an activity

To create a new activity, a client posts the activity in JSON format to the user's feed. The Activity Pump will automatically add IDs where needed and the user's profile as an actor.

Here is a example HTTP request to create a new activity:

POST /api/user/bwk/feed HTTP/1.1
Host: coding.example
Authorization: OAuth oauth_consumer_key="[...]",
    oauth_token="[...]", [...]
Content-Type: application/json

{
    "verb": "follow",
    "object": {
        "id": "acct:ken@coding.example",
        "objectType": "person"
    }
}

Note that the request uses OAuth authorization to authenticate the user. The only form of authentication allowed for the activity outbox is OAuth; the user must authorize the client before new activities are created.

The HTTP response to this request will be a fully-defined activity with all the IDs and timestamps filled in.

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

{
    "id": "http://coding.example/api/activity/bwkflwken",
    "actor": {
        "id": "acct:bwk@coding.example",
        "objectType": "person",
        "displayName": "Brian Kernighan"
    },
    "verb": "follow",
    "to": [{
        "id": "acct:ken@coding.example",
        "objectType": "person"
    }],
    "object": {
        "id": "acct:ken@coding.example",
        "objectType": "person",
        "displayName": "Ken Thompson"
    },
    "published": "1974-01-01T00:00:00",
    "links": [
        {"rel": "self", "href": "http://coding.example/api/activity/bwkflwken"}
    ]
}

Especially for finding links or IDs, the response can be really valuable.

Side-effects

Posted activities may have side-effects; in the above case, the actor "bwk" has followed another person, "ken", so that activities that "ken" shares with his followers will also go to "bwk"'s inbox.

Most activity verbs don't have side-effects. In this case, the pump.io will just store the data about the activity, and distribute the activity according to the social graph, but it won't change the state of that graph.

Verbs that have side-effects include:

  • "post": Creates the object.
  • "update": Modifies the object, if it exists, to have the new structure in this activity.
  • "delete": Deletes the object. After a delete, only a shell of the data about the object will remain.
  • "follow": Makes the actor follow the object. After a follow, activities that the object shares with his/her followers will go to the actor's inbox. Like a subscription. The actor is added to the object's followers collection, and the object is added to the actor's following collection.
  • "stop-following": Makes the actor stop following the object. After this point, activities that the object shares with his/her followers will not go to the actor's inbox, but any existing activities will stay there.
  • "favorite" or "like": These are synonyms. The actor is added to object's list of "likers", and the object is added to the actor's list of favorites.
  • "unfavorite" or "unlike": Undoes a "like" or "favorite".
  • "add": If the target is a collection that belongs to the actor, will add the object to the collection. Good for adding users to user lists.
  • "remove": If the target is a collection that belongs to the actor, will add the object to the collection. Good for removing users from user lists.

Other verbs may have side-effects in the future -- especially the ones around friendships, groups, events, and playing media.

Reading collections

Reading from the activity outbox or activity inbox requires OAuth authentication. The activity inbox requires user authorization; you can request data from the activity outbox using plain old 2-legged OAuth client authentication if you want.

items in collections are in roughly reverse chronological ("newest first") order.

Arguments

Collection URLs can have arguments that change the size of the collection. By default, the collection returned will include only the most recent activities -- usually 20.

Collection URLs take the following params:

  • count. The number of items to return (default is usually 20). This maxes out at 200, usually.
  • offset. Zero-based offset specifying where in the collection you want to start. Default 0. This is a bad way to do pages, since activities are added at the beginning of the collection.
  • before. An activity ID. Will get activities that went into the collection immediately before the specified activity (not inclusive). A good way to "scroll back" in a collection.
  • since. An activity ID. Will get activities that went into the collection immediately after the specified activity (not inclusive). A good way to get what's new in a collection since you last polled it.

Navigation links

Collection objects include links to help with navigation, using the Multi-page Collections schema.

Note that because activities are in reverse chronological order, understanding what's "next" or "previous" is kind of unintuitive. We assume you start with the current activities and scroll backwards in time, so "older" stuff is "next" and "newer" stuff is "prev".

  • next The next older group of activities.
  • prev The next newer group of activities. This is provided even if you're looking at the newest activities; it makes it easier to just get the most recent stuff you haven't seen.

It's a good idea to use these links if you're navigating through a collection; the chances that both you and I will get all the arguments correct in our heads is probably pretty small.

Addressing activities

pump.io supports the Audience Targeting for JSON Activity Streams extension, which lets you add addresses to an activity to show to whom the activity is directed.

Address properties for an activity include to, cc, bto, and bcc. The properties are arrays [] of objects {}. The objects should include an id and objectType property; they may include other properties.

Activities that are to a user or list will go into their "direct" inbox (see below).

bto and bcc properties won't be shown to any users except the author.

pump.io uses the addresses for three things:

  • Delivery of activities. Depending on the addresses, the activity will be delivered either to local user inboxes or to remote users (see below!).
  • Access control to activities. Requesting an activity directly from its REST endpoint will give a 403 error. Also, feeds are filtered to only show activities that were addressed to the requester.
  • Access control to objects. Access to objects generally depends on if the requester was a recipient of the "post" activity that created it. The REST endpoint for an object (see below) will return a 403 status code otherwise.

Access control with addresses is inclusive; if an activity is to

Types of address

There are four types of address supported:

  • The public. "The public" is an object with objectType equal to "collection" and the special ID "http://activityschema.org/collection/public". Activities addressed to the public will be delivered to all followers, and will be visible to anyone -- even unauthenticated users.
  • Followers. A user's own followers can be addressed with an object with objectType equal to "collection" and id equal to the follower stream URL of the user -- usually http://<hostname>/api/user/<nickname>/followers. Activities addressed to followers will be delivered to all followers, and will only be visible to followers.
  • Users. Users can be addressed by their profile object -- objectType is person, and id is something like acct:<nickname>@<hostname>.
  • Lists. Users can create collections of people or other objects. An address with objectType "collection" and the ID of one of the actor's own collections will result in delivery to the members of that list, and members of the list will be allowed to view the activity and/or object.

Default addresses

Default addresses are added if an activity is posted to the activity outbox with no address properties. We try to be reasonable with the defaults, as follows:

  • If an activity has an object that is inReplyTo another object, the addresses for the "post" activity of the original are used.
  • If an activity is an "update" or a "delete" of an object, the addresses for the "post" activity of that object are used.
  • If an activity has an object that is a "person", the person is added as a to address.
  • Otherwise, the actor's followers are added as a cc address.

These defaults will probably change over time; if you want to make sure that specific addresses are used, you should definitely add them explicitly.

Major and minor feeds

Some activities are more important than others. pump.io provides sub-feeds of the outbox and inbox, divided by whether the activity is "major" or "minor". Roughly speaking, posting new content is "major", and changes to the social graph or reactions to other content are "minor".

The feeds are at /api/user/<nickname>/inbox/major, /api/user/<nickname>/inbox/minor, /api/user/<nickname>/feed/major, and /api/user/<nickname>/feed/minor.

The major and minor feeds will respond to POST requests. You can only post major activities to the major feed and minor activities to the minor feed.

Direct inbox

Activities that have a to or bto property that includes the user's address will be listed in the user's "direct" inbox. There are major and minor variations of this inbox, also.

The feeds are at /api/user/<nickname>/inbox/direct, /api/user/<nickname>/inbox/direct/minor, and /api/user/<nickname>/inbox/direct/major.

Object endpoints

When objects like a "note" or an "image" are created, they're assigned a REST endpoint, usually something like http://<hostname>/api/<objectType>/<id>, where the <id> is a screwy-looking random value. (It's a UUID in URL-safe base-64 format.)

You can get the object endpoint from the object's links property; it's the link with rel value self.

Objects respond to HTTP GET requests with an Activity Streams JSON representation of the object.

The author of an object can PUT to the object endpoint; this will update the object; it will also generate an "update" activity.

The author of an object can DELETE to the object endpoint; this will delete the object. It will also generate a "delete" activity. Sending a GET to an object endpoint for an object that was deleted will return a 410 Gone status code.

Links

Objects also have a links property; it's an array of objects.

  • self. The canonical, true, real, one-and-only for-sure HTTP endpoint to retrieve an Activity Streams JSON representation of the object.

Collection properties

Objects have related collections, as defined in the misnamed Responses for Activity Streams. These particular properties are probably interesting:

  • replies. Objects that were posted with an inReplyTo value of this object.
  • likes. People who have sent a "favorite" activity with this object as the object property.

In representations, these collections will use have the first ~4 items included in the items.

"person" objects have these collections instead.

  • followers. People who follow this person.
  • following. People who this person is following.
  • lists. (non-standard) Lists that belong to the user.
  • favorites. (non-standard) Objects that the user has sent a "favorite" activity about.

Just for fun's sake, a user can follow someone else by posting to their followers collection. They can also add a favorite by posting the object to their favorites collection. Both of these will generate the appropriate activity with default addresses.

"collection" objects have this property:

  • members. The collection of members of the collection (yes, it's weirdly recursive).

"group" objects have these properties:

  • members. The members of the group.
  • inbox. The activities that have been posted with this group as a recipient.
  • documents. The objects that have been posted with this group as a target.

Foreign IDs

You can create objects with a foreign ID -- an ID that you make up. This can make it a lot easier to store objects that mirror external objects; for example, a document in an exterior database.

Your foreign IDs should be URIs, and you should make sure they're unique. tag URIs are great if you're matching an external namespace. For example, if you have an application with a SQL database with an auto-incrementing primary key, you could have tag URIs like

tag:yourapp.example,2013:image:2342

UUID URNs are great for random values or for hashes of values you don't want to expose or that are too long.

If you use objects with foreign IDs, you can get the data for that object from an endpoint like:

`http://<hostname>/api/<objectType>?id=<the-object-id>`

The main object feeds be accessed this way as well:

`http://<hostname>/api/<type>/likes?id=<the-object-id>`
`http://<hostname>/api/<type>/replies?id=<the-object-id>`
`http://<hostname>/api/<type>/shares?id=<the-object-id>`

Group objects have feed endpoints that can be accessed this way as well:

`http://<hostname>/api/group/members?id=<the-object-id>`
`http://<hostname>/api/group/inbox?id=<the-object-id>`
`http://<hostname>/api/group/documents?id=<the-object-id>`

Activity endpoints

Every activity also has a REST endpoint, usually http://<hostname>/api/activity/<uuid>.

It will respond to a GET with the JSON representation of the activity.

The REST endpoints also respond to PUT or DELETE requests. These won't cause side-effects, however; deleting a "follow" activity won't change the list of followers. It's probably much better to post another activity, such as "stop-following", to reverse the effects of an activity.

Authentication

Almost all API endpoints require OAuth authentication; most of them require user authorization. The OAuth sign-up flow is pretty straightforward, with the following endpoints:

  • /oauth/request_token to get an OAuth request token
  • /oauth/authorize to authorize an OAuth request token
  • /oauth/access_token to turn a request token into an access token

I can barely understand OAuth 1.0 and I can't figure out OAuth 2.0 at all, so I'm sticking with 1.0.

To get profile data on the authenticated user, use the endpoint at:

  • /api/whoami - returns an an activity object for the registered user. Uses a redirect to the canonical endpoint, so you should follow that.

2-legged OAuth

The following endpoints only require 2-legged authentication; you don't have to get user authorization or provide an oauth_token parameter. However, getting a user authorization will allow getting some stuff that's otherwise private.

  • activity outbox
  • followers collection
  • following collection
  • favorites collection

Client registration

You can request a new client ID for OAuth authentication automatically, using OpenID Connect Dynamic Client Registration. Note there's nothing OpenID-ish about this; I just needed a dynamic client registration system and used (some of) this spec.

The registration endpoint is at /api/client/register. You can also discover it in the host-meta file with link-rel "registration_endpoint".

The client registration will accept some of the parameters that OpenID does. Here's what it supports:

  • type: one of the values client_associate (when registering a client) or client_update (when updating the details of a previously-registered client)
  • client_id, client_secret: only when type is set to client_update.
  • contacts
  • application_type
  • application_name
  • logo_url
  • redirect_uris

Dialback authentication

If you use Dialback Access Authentication when requesting an OAuth client identity, the client ID will be associated with the host or webfinger that you use for authentication.

You can then make 2-legged OAuth requests (no oauth_token) to other parts of the API, with the authorization of that webfinger or host.

This currently only works with remote delivery (see below), but probably other parts of the API will support it in the future.

User registration

There is a collection of all users at the endpoint /api/users. To create a new user, POST a user representation (see below) to the list. You can also get the latest registered users by GETting the collection.

The JSON object representing the user has the following properties:

  • nickname: The user's nickname. 1-64 characters, including only ASCII capital and lowercase letters and numbers as well as "-", ".", and "_". The nickname is immutable and unique per server; it can't be changed.
  • password: The plain-text password. This isn't returned when you GET the user object, but you have to provide it when registering or updating the user.
  • profile: a "person" object. This is created automatically when you create a new user; don't try to add it yourself. Don't update this directly; update the person through its object endpoint.

User objects

Each user has an HTTP endpoint at /api/user/<nickname>. It's useful to GET the user representation or to get the profile for a user.

The user can PUT a new representation; since nickname and profile are immutable, this is pretty much only useful to change your password.

The user can DELETE the endpoint; it will delete the user account, but not much else. In particular, it won't clean up all the user's activities, profile, followers, following, lists, or published objects.

Discovery

pump.io supports Web Host Metadata to discover information about users and hosts. It supports the XRD and JRD versions of the discovery output; JRD is probably better to use.

For hosts, we provide these link-rel types:

  • lrdd - to find the endpoint for per-user discovery
  • registration_endpoint - to find the endpoint for client registration
  • dialback - to find the endpoint for Dialback authentication verification for this host

For users, we support only <nickname>@<hostname> ID format. These link-rel types are provided:

  • http://webfinger.net/rel/profile-page - to find the profile page
  • activity-inbox - the user's activity inbox
  • activity-outbox - the user's activity outbox
  • dialback - for Dialback verification for this user

Remote delivery

It's possible to deliver activities from a remote host to a user's inbox by POSTing to the inbox. You need to use 2-legged OAuth authentication, and the client ID used for the OAuth has to be associated with the webfinger ID of the actor who did the activity.

When activities are posted to a user's activity outbox with addresses of remote users, pump.io tries to deliver them using this method. In rough outline:

  1. It tries to discover an activity-inbox link for the person.
  2. It tries to discover a registration_endpoint for the hostname part of the activity-inbox endpoint.
  3. It registers new OAuth credentials for the sender with the remote host.
  4. It POSTs the activity to the person's activity inbox endpoint, with the OAuth credentials.
Something went wrong with that request. Please try again.