How To Use User Streams

User streams are long-lasting websocket connections between the API and user-facing clients, for near-realtime interaction. They are similar to App Streams but for individual users instead of for a central server that processes data for users.

Create a Connection

To create a stream, the client must start a new websocket on the endpoint wss://stream.pnut.io/v1/user. The first message across this connection will be JSON like this:

{
  "meta": {
    "stream": {
      "endpoint": "wss://stream.pnut.io/v1/user"
    },
    "connection_id": "5AjC6sMJrR2VueG3lHmiDTTECjzMr68i"
  }
}

Store the meta.connection_id for creating subscriptions next.

Create Subscriptions

Now the client needs to subscribe to each endpoint you want to receive websocket responses for. Do this by calling each endpoint the same as you would if polling, once, with your connection_id from above included as a query parameter on the call.

Example
curl "https://api.pnut.io/v1/users/me/posts?connection_id=5AjC6sMJrR2VueG3lHmiDTTECjzMr68i" \
    -H "Authorization: Bearer ${ACCESS_TOKEN}" \
    -X GET \
    -H "X-Pretty-Json: 1"

Returns the normal response from the endpoint, with meta.subscription_id.

{
  "meta": {
    "more": true,
    "max_id": "2814",
    "min_id": "2795",
    "subscription_id": "So2e3-W76iI8nuX1UWDer_RpzVYPhxDS",
    "code": 200
  },
  "data": []
}

The user stream will expect to receive a message back from your server on the websocket every 60 seconds or less, to keep the connection open. If it disconnects, you will simply have to reconnect and either track your position before a disconnect and backfill with regular API calls, or skip what was missed in between.

Currently, streams auto-delete themselves on disconnect. If you lose connection, you will have to rebuild it with subscriptions again.

A connection may have up to 16 subscriptions.

A connection may subscribe to an endpoint more than once (with or without different parameters), so be sure to prevent that if it isn't helpful to you.

Miscellany

Subscribable Endpoints

  • /users/me/posts
  • /users/me/mentions
  • /posts/:post_id/thread
  • /posts/streams/me
  • /posts/streams/unified
  • /users/me/following
  • /users/me/followers
  • /channels/:channel_id/messages
  • /channels (includes new messages for channels you're subscribed to)
  • /channels/:channel_id/subscribers
  • /channels/:channel_id/presence
  • /token (includes updates for both the token and the user objects of the current user)
  • /users/me/files
  • /presence

Connection Query Parameters

  • include_raw
  • include_post_raw
  • include_user_raw
  • include_file_raw
  • include_channel_raw
  • include_message_raw
  • include_bookmarked_by
  • include_reposted_by
  • include_marker
  • include_recent_message
  • include_html

Subscription Query Parameters

  • include_incomplete (files)
  • include_private (files)
  • include_not_followed (user presence)
  • channel_types
  • file_types
  • include_read
  • include_muted
  • include_deleted
  • include_directed_posts

Example Objects

post

Sent when any post is created, reposted, revised, or deleted.

Reposts will send the newly created or deleted repost and include repost_of in the post object.

Newly revised posts will include meta.revision.

Newly deleted posts will include meta.is_deleted.

{
  "data": [
    "...post object..."
  ],
  "meta": {
    "timestamp": 1534019640,
    "id": "2834",
    "subscription_ids": [
      "gJ1EUufwPXrK9N34ulTRcx5NHco6D7FE"
    ]
  }
}

follow

Sent when any user follows or unfollows another user. Regardless of whom the notification goes to, the fields will be the same. data.user is the user following or unfollowing, of course.

{
  "data": {
    "followed_user": "...user object...",
    "user": "...user object..."
  },
  "meta": {
    "id": "9",
    "timestamp": 1534017586,
    "subscription_ids": [
      "JUnEC5cmqiJY7Wu5uKNAmNH-AXVP6GAo"
    ]
  }
}

message

Sent when a message is created or deleted from a channel.

channel_type is included in the meta.

channel_name is included if the channel is a chat room (of type io.pnut.core.chat).

{
  "data": [
    "...message object..."
  ],
  "meta": {
    "timestamp": 1534019721,
    "channel_type": "io.pnut.core.pm",
    "id": "71",
    "subscription_ids": [
      "LklDY7v5zAhPYNEUZlzb01oZA_3BnYvH",
      "u7F6LFhFfTrVMBDQRkIXYPGca_I-__EW"
    ]
  }
}

channel subscription

Sent when a user subscribes to or unsubscribes from a channel. Currently only includes the channel object, not the user whose subscription changed.

{
  "data": "...channel object...",
  "meta": {
    "timestamp": 1534020228,
    "id": "22",
    "subscription_ids": [
      "_CGQ8XARMPMNqqQUaLl7GI3Utr4Bmkec"
    ]
  }
}

token

Sent when you authorize a new token, or update your user object.

{
  "data": "...token object...",
  "meta": {
    "timestamp": 1534020401,
    "id": "1",
    "subscription_ids": [
      "d2Zn0uypyWVf0WCIHyLIXW0YDTtKuNOV"
    ]
  }
}

file

Sent when a user uploads a file, updates file details, uploads a file, or deletes a file.

{
  "data": "...file object...",
  "meta": {
    "timestamp": 1534020233,
    "id": "349",
    "subscription_ids": [
      "rqDNgxAQASRCIBqHZ5Dek71SBar-wPjX"
    ]
  }
}

user_presence

Sent when a user changes their user presence. Currently does not send when a user sets themselves to "offline" or when the expiration is reached.

By default, GET /presence subscribes only to user presence updates from followed users (including yourself). Including ?include_not_followed=1 will include updates from anyone not blocked.

{
  "data": [
    "...user presence object..."
  ],
  "meta": {
    "timestamp": 1678811013,
    "id": "1",
    "subscription_ids": [
      "frenSxAQASRCIBqHZ5Dek71SBar-efke"
    ]
  }
}

user_channel_presence

Sent when a user changes their user presence within a channel you have a user stream subscription for. Currently does not send when a user sets themselves to "offline" or when the expiration is reached.

By default, GET /channels/:channel_id/presence subscribes only to user presence updates from followed users (including yourself). Including ?include_not_followed=1 will include updates from anyone not blocked.

{
  "data": [
    "...user presence object..."
  ],
  "meta": {
    "timestamp": 1678811013,
    "id": "1",
    "subscription_ids": [
      "frenSxAQASRCIBqHZ5Dek71SBar-efke"
    ]
  }
}