Homepage: https://jman012.github.io/FloatplaneAPIDocs
This document describes the asynchronous/event-driven API layer of https://www.floatplane.com, a content creation and video streaming website created by Floatplane Media Inc. and Linus Media Group, where users can support their favorite creates via paid subscriptions in order to watch their video and livestream content in higher quality and other perks.
This API is specific to the frontend activities of the Floatplane website, which is responsible for pushing new-post notifications to the client, and is meant for a connection to www.floatplane.com. If you are looking for the chat/livestream async API layer of Floatplane (chat.floatplane.com), please visit this document instead.
This document is an AsyncAPI implementation on top of a Socket.IO connection. AsyncAPI does not have any specifics for Socket.IO, and so any automatic code-generation from this document may not work 100% out-of-the-box on a WebSocket connection. At best, it will provide all of the proper models needed for the different events and messages. But it may require some glue code to get it working properly with Sails. See the below section on the difference between Socket.IO, Sails, and Floatplane for this socket connection.
It is recommended for any client implementation to use a Socket.IO reference library implementation in your language of choice with this document. A version of this document for Sails would be preferred, but there is only a single client reference implementation (JavaScript), and the Sails layer on top of Socket.IO is fairly straightforward.
Floatplane's backend primarily uses Sails and the website UI uses an Angular frontend along with Sails' socket connection for low-latency request/response and event-driven architecture. Sails is an MVC framework for making Node.js websites and APIs. Sails' socket connection is built on three tiers of technology that should be understood when implementing this API:
0
prefix, pings/pongs as a single byte (2
/3
), or messages with the 4
prefix. If you are analyzing the WebSocket frames directly, it would be beneficial to familiarize yourself with the Engine.IO protocol.2
, acknowledgements are prefixed with 3
(both are after the Engine.IO prefixes/headers), and the data of the event is stored as a JSON-encoded array of items, where the first item is always a string identifying the event name, and optional subsequent items are the arguments to the event.GET
, POST
, etc. onto the socket connection, and uses Socket.IO's acknowledgements in order to send back a response body."get"
, "post"
, etc.), and the first and only argument to the Socket.IO event is a data structure with Sails-specific fields: method
, headers
, url
, and data
. The data
field is where the application-specific data is stored in the event.body
, headers
, and statusCode
fields as the first and only argument in the ack.The following list shows some examples of what a raw WebSocket connection might entail, and splits up the data between the various layers. This section is mostly to help with debugging raw connections, and understanding the technical stack of Floatplane.
GET wss://www.floatplane.com/socket.io/?__sails_io_sdk_version=0.13.8&__sails_io_sdk_platform=browser&__sails_io_sdk_language=javascript&EIO=3&transport=websocket
HTTP 101 Switching Protocols
.0{"sid":"b1VcCLtZ1SUXiYEGAB49","upgrades":[],"pingInterval":25000,"pingTimeout":60000}
0
: Engine.IO open header.{"sid":"b1VcCLtZ1SUXiYEGAB49","upgrades":[],"pingInterval":25000,"pingTimeout":60000}
: Engine.IO open payload.40
/
).4
: Engine.IO message header.0
: Socket.IO connect header./
is implied but not included in this message due to the Socket.IO protocol marking is as optional. The non-encoded message for this in Socket.IO would look like {"type": 0,"nsp": "/admin","data": {}}
.420["post",{"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}]
4
: Engine.IO message header.20
: Socket.IO header.2
: Marks that this message is an Event.0
: Id parameter. Needed for the following acknowledgement.["post",{"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}]
: Socket.IO event data."post"
: Socket.IO event name.{"method":"post","headers":{},"data":{},"url":"/api/v3/socket/connect"}
: Socket.IO event argument for the Sails "post"
event."method":"post,"url":"/api/v3/socket/connect"
: This Sails POST request is destined for the /api/v3/socket/connect
endpoint."data":{}
: Sails request body.{}
: No Floatplane data in the request.430[{"body":{"message":"User sync setup."},"headers":{},"statusCode":200}]
4
: Engine.IO message header.30
: Socket.IO header.3
: Marks that this message is an Acknowledgement0
: Id parameter. Marks that it is acknowledging event number 0.[{"body":{"message":"User sync setup."},"headers":{},"statusCode":200}]
: Socket.IO acknowledgement data.{"body":{"message":"User sync setup."},"headers":{},"statusCode":200}
: The only argument in the Socket.IO ack"statusCode":200
: Sails HTTP response status code."body":{"message":"User sync setup."}
: Sails HTTP response body.{"message":"User sync setup."}
: Floatplane connection response.421["syncEvent",{"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}]
4
: Engine.IO message header.2
: Socket.IO event header.["syncEvent",{"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}]
: Socket.IO event data."syncEvent"
: Socket.IO event name.{"event": "creatorNotification","data": {"id": "CONTENT_POST_RELEASE:yQDi4v3EMc","eventType": "CONTENT_POST_RELEASE",...}}
: Socket.IO event argument for the "syncEvent"
event.This document is primarily organized around the AsyncAPI blog post on Socket.IO (Part 1, and Part 2). The extension x-ack
is used in order to model Socket.IO's acknowledgement feature that emulates the request/response paradigm. The Socket.IO acknowledgement does not have an event name that goes over the wire, and instead directly references the initiating event via its unique identifier. As such, acknowledgements are not specified in the AsyncAPI's channel's publisher or subscriber operators.
This document also manually incorporates features of Sails on top of Socket.IO. It does so manually because the Sails abstraction is rather light, by basically specifying event names and models for data to pass through for requests and responses to virtual endpoints. Sails introduces the event names "get"
, "post"
, "put"
, and other HTTP verbs, where the event argument (request) is JSON in the form of:
{
"method": "get",
"headers": {},
"body": {
...
},
"url": "/api/v3/..."
}
And the acknowledgement/response argument is JSON in the form of:
{
"body": {
...
},
"headers": {},
"statusCode: 200
}
Where body
in each is specific to Floatplane. The rest of the data structure emulates HTTP requests and responses. As such, this AsyncAPI document explicitly models these structures around the Floatplane-specific requests and responses.
Finally, because Sails uses generic "get"
, "post"
, etc. event names for multiple types of actualized events on Socket.IO, a single AsyncAPI Operator is defined for each of "get"
and "post"
, and uses JSON Schema's oneOf
feature for multiple kinds of body
models, one for each request/response.
Useful links for AsyncAPI and WebSockets/Socket.IO:
When configuring a Socket.IO connection for use with Floatplane, there are some particular configurations to perform in order to prevent issues.
/socket.io
/socket.io
./engine.io
and may result in an HTTP 404 if used.true
websocket
/ Secure: true
__sails_io_sdk_version
: 0.13.8
__sails_io_sdk_platform
: {your platform}__sails_io_sdk_language
: {your language}0.9.0
which will throw an error when performing an Sails-related events, because it thinks your code will be too old to handle it.Origin: https://www.floatplane.com"
extraHeaders
configuration.sails.sid
sails.sid
cookie is not required to make a raw Socket.IO connection with Floatplane, but will be required for making most Sails get/post requests.A client connects to Floatplane's asynchronous API via WebSockets with TLS (wss). The socket it connects to is goeverned by Sails, which requires specifying which version of Sails to use, as well as other parameters like the platform and language. The purpose of the EIO
query parameter is unknown. If using a proper Sails client library, the parameters should be auto-filled for you, and simply connecting to www.floatplane.com/socket.io/
should suffice.
The value 0.13.8
is the current value at the time of writing (2022-05-08). This gets updated every so often with updates to the Floatplane frontend. There may be compatibility issues if too old of a value is supplied (the Sails backend may reject the connection). It is important to keep this value as up-to-date as possible, in order to prevent rejected connection issues.
The value browser
is the current value used by the Sails JS client library. It is not known what effect values other than those defined by Sails may have on the socket connection.
The value javascript
is the current value used by the Sails JS client library. It is not known what effect values other than javascript
may have on the socket connection.
Socket.IO and Sails groups messages and events into different namespaces, with /
being the default namespace. Multiple kinds of messages can be sent in a single namespace, with Socket.IO having its own mechanisms to differentiate message types. Floatplane only uses the root Socket.IO namespace (/
).
Accepts the following message:
HTTP POST via Sails socket connection
This one asynchronous Sails/Socket.IO event may contain different Floatplane events in the payload, and contain different Floatplane events in the response/acknowledgement (depending on the request).
Connecting to Floatplane sync events via a Sails HTTP POST.
{
"method": "post",
"headers": {},
"data": {},
"url": "/api/v3/socket/connect"
}
Disconnect from Floatplane sync events via a Sails HTTP POST.
{
"method": "post",
"headers": {},
"data": {},
"url": "/api/v3/socket/disconnect"
}
Connect to a live poll room via a Sails HTTP POST.
{
"method": "post",
"headers": {},
"data": {
"creatorId": "59f94c0bdd241b70349eb72b"
},
"url": "/api/v3/poll/live/joinroom"
}
Disconnect from a live poll room via a Sails HTTP POST.
{
"method": "post",
"headers": {},
"data": {
"creatorId": "59f94c0bdd241b70349eb72b"
},
"url": "/api/v3/poll/live/leaveroom"
}
Socket.IO and Sails groups messages and events into different namespaces, with /
being the default namespace. Multiple kinds of messages can be sent in a single namespace, with Socket.IO having its own mechanisms to differentiate message types. Floatplane only uses the root Socket.IO namespace (/
).
Accepts one of the following messages:
A sync event arrives from the Floatplane server after registrating with the connect event (`/api/v3/socket/connect`).
The sync event is another wrapper over Socket.IO, where the actual event type is embedded within the Socket.IO event payload. Most kinds of sync events are notifications of new blog posts on Floatplane from a subscribed creator.
Floatplane sent a sync event for a content post release via the `creatorNotification` event.
{
"event": "creatorNotification",
"data": {
"id": "CONTENT_POST_RELEASE:yQDi4v3EMc",
"eventType": "CONTENT_POST_RELEASE",
"title": "New post from LinusTechTips",
"message": "Reacting to OLD Computer Magazines!",
"creator": "59f94c0bdd241b70349eb72b",
"content": "yQDi4v3EMc",
"icon": "https://pbs.floatplane.com/creator_icons/59f94c0bdd241b70349eb72b/770551996990709_1551249357205.jpeg",
"thumbnail": "https://pbs.floatplane.com/blogPost_thumbnails/yQDi4v3EMc/682332670704114_1651513307946.jpeg",
"target": {
"url": "/post/yQDi4v3EMc",
"matchScheme": "contains",
"match": "yQDi4v3EMc",
"foregroundDiscardOnMatch": true,
"matchPortion": "path"
},
"foregroundVisible": "yes",
"video": {
"creator": "59f94c0bdd241b70349eb72b",
"guid": "yQDi4v3EMc"
},
"post": {
"creator": "59f94c0bdd241b70349eb72b",
"guid": "yQDi4v3EMc",
"id": "yQDi4v3EMc",
"text": "<p>Join us on a trip down memory lane where Linus looks at some of the greatest and latest PC tech circa 2004. These old issues of Maximum PC are filled with tons of hilarious takes, forgotten products, and beloved childhood favourites!</p>",
"title": "Reacting to OLD Computer Magazines!"
}
}
}
Floatplane sent a sync event for a content livestream start via the `creatorNotification` event.
{
"event": "creatorNotification",
"data": {
"id": "CONTENT_LIVESTREAM_START:91ecb0f8-541b-4c5b-b35f-05552df7f805",
"eventType": "CONTENT_LIVESTREAM_START",
"title": "LinusTechTips started a livestream",
"message": "We're Finally Free - WAN Show May 13, 2022",
"creator": "59f94c0bdd241b70349eb72b",
"icon": "https://pbs.floatplane.com/creator_icons/59f94c0bdd241b70349eb72b/770551996990709_1551249357205.jpeg",
"thumbnail": "https://pbs.floatplane.com/stream_thumbnails/5c13f3c006f1be15e08e05c0/481046370800602_1651880382456.jpeg",
"target": {
"url": "/channel/linustechtips/live/",
"matchScheme": "contains",
"match": "linustechtips",
"foregroundDiscardOnMatch": true,
"matchPortion": "path"
},
"foregroundVisible": "yes"
}
}
Floatplane sent a sync event for a content post release via the `postRelease` event.
{
"event": "postRelease",
"data": {
"id": "CONTENT_POST_RELEASE:yQDi4v3EMc",
"eventType": "CONTENT_POST_RELEASE",
"title": "New post from LinusTechTips",
"message": "Reacting to OLD Computer Magazines!",
"creator": "59f94c0bdd241b70349eb72b",
"content": "yQDi4v3EMc",
"icon": "https://pbs.floatplane.com/creator_icons/59f94c0bdd241b70349eb72b/770551996990709_1551249357205.jpeg",
"thumbnail": "https://pbs.floatplane.com/blogPost_thumbnails/yQDi4v3EMc/682332670704114_1651513307946.jpeg",
"target": {
"url": "/post/yQDi4v3EMc",
"matchScheme": "contains",
"match": "yQDi4v3EMc",
"foregroundDiscardOnMatch": true,
"matchPortion": "path"
},
"foregroundVisible": "yes",
"video": {
"creator": "59f94c0bdd241b70349eb72b",
"guid": "yQDi4v3EMc"
},
"post": {
"creator": "59f94c0bdd241b70349eb72b",
"guid": "yQDi4v3EMc",
"id": "yQDi4v3EMc",
"text": "<p>Join us on a trip down memory lane where Linus looks at some of the greatest and latest PC tech circa 2004. These old issues of Maximum PC are filled with tons of hilarious takes, forgotten products, and beloved childhood favourites!</p>",
"title": "Reacting to OLD Computer Magazines!"
}
}
}
Floatplane sent a sync event for a creator menu update.
{
"event": "creatorMenuUpdate",
"data": {
"id": "yQDi4v3EMc",
"guid": "yQDi4v3EMc",
"title": "Reacting to OLD Computer Magazines!",
"text": "<p>Join us on a trip down memory lane where Linus looks at some of the greatest and latest PC tech circa 2004. These old issues of Maximum PC are filled with tons of hilarious takes, forgotten products, and beloved childhood favourites!</p>",
"type": "blogPost",
"tags": [],
"attachmentOrder": [
"lb7QsgcEtc"
],
"metadata": {
"hasVideo": true,
"videoCount": 1,
"videoDuration": 1096,
"hasAudio": false,
"audioCount": 0,
"audioDuration": 0,
"hasPicture": false,
"pictureCount": 0,
"hasGallery": false,
"galleryCount": 0,
"isFeatured": false
},
"releaseDate": "2022-05-02T19:44:00.043Z",
"likes": 0,
"dislikes": 0,
"score": 0,
"comments": 0,
"creator": "59f94c0bdd241b70349eb72b",
"wasReleasedSilently": false,
"thumbnail": {
"width": 1920,
"height": 1080,
"path": "https://pbs.floatplane.com/blogPost_thumbnails/yQDi4v3EMc/682332670704114_1651513307946.jpeg",
"childImages": [
{
"width": 400,
"height": 225,
"path": "https://pbs.floatplane.com/blogPost_thumbnails/yQDi4v3EMc/682332670704114_1651513307946_400x225.jpeg"
},
{
"width": 1200,
"height": 675,
"path": "https://pbs.floatplane.com/blogPost_thumbnails/yQDi4v3EMc/682332670704114_1651513307946_1200x675.jpeg"
}
]
}
}
}
A `pollOpen` event arrives when a creator has started a new poll for users to vote on.
This provides options for the user to choose from, when the poll opened, and when it will automatically close. If the endDate
is reached, the UI should show the conclusion and hide the poll as there will be no corresponding PollClose event. The startDate
and endDate
can be used to show a timer in the UI. In most polls, only a single option is permitted to be voted on, and the user is only allowed one chance at a vote.
In order to vote on a poll, perform an HTTP POST to /api/v3/poll/vote
. Look at the OpenAPI specification document for more information.
This schema is used for both PollOpen and PollClose.
Opening a new poll with three options, with 60 seconds to answer.
{
"poll": {
"id": "62c8c1dd968bc0899bbb4b92",
"type": "simple",
"creator": "59f94c0bdd241b70349eb72b",
"title": "When will WAN go live?",
"options": [
"5:00",
"5:30",
"It's already live 👽"
],
"startDate": "2022-07-08T23:46:37.429Z",
"endDate": "2022-07-08T23:47:37.428Z",
"finalTallyApproximate": null,
"finalTallyReal": null,
"runningTally": {
"tick": 0,
"counts": [
0,
0,
0
]
}
}
}
A `pollClose` event arrives when a creator has ended a poll earlier than the expected `endDate`. Sometimes this happens right before starting a new poll, as it appears only one poll can be active at a time, per-creator. This payload will include the same details as the PollOpen event, but with an actual `endDate` as well as the final results of the tally.
This schema is used for both PollOpen and PollClose.
Closing a two-option poll early with a final count of 38 to 48. It ended about 17 seconds into the poll.
{
"poll": {
"id": "62c8e33a9f58a79f5269bbd7",
"type": "simple",
"creator": "59f94c0bdd241b70349eb72b",
"title": "Should they do a poll? ",
"options": [
"Yes",
"Different Yes"
],
"startDate": "2022-07-09T02:08:58.784Z",
"endDate": "2022-07-09T02:09:15.514Z",
"finalTallyApproximate": null,
"finalTallyReal": null,
"runningTally": {
"tick": 86,
"counts": [
38,
48
]
}
}
}
A `pollUpdateTally` even will arrive upon every vote on the poll by a user.
The tick
value will increment by one for each event, and one of the option counters should increment by one due to each vote.
The first vote performed on a poll, voting for the third option.
{
"tick": 1,
"counts": [
0,
0,
1
],
"pollId": "62c8c1dd968bc0899bbb4b92"
}
HTTP POST via Sails socket connection
This one asynchronous Sails/Socket.IO event may contain different Floatplane events in the payload, and contain different Floatplane events in the response/acknowledgement (depending on the request).
A sync event arrives from the Floatplane server after registrating with the connect event (`/api/v3/socket/connect`).
The sync event is another wrapper over Socket.IO, where the actual event type is embedded within the Socket.IO event payload. Most kinds of sync events are notifications of new blog posts on Floatplane from a subscribed creator.
A `pollOpen` event arrives when a creator has started a new poll for users to vote on.
This provides options for the user to choose from, when the poll opened, and when it will automatically close. If the endDate
is reached, the UI should show the conclusion and hide the poll as there will be no corresponding PollClose event. The startDate
and endDate
can be used to show a timer in the UI. In most polls, only a single option is permitted to be voted on, and the user is only allowed one chance at a vote.
In order to vote on a poll, perform an HTTP POST to /api/v3/poll/vote
. Look at the OpenAPI specification document for more information.
This schema is used for both PollOpen and PollClose.
A `pollClose` event arrives when a creator has ended a poll earlier than the expected `endDate`. Sometimes this happens right before starting a new poll, as it appears only one poll can be active at a time, per-creator. This payload will include the same details as the PollOpen event, but with an actual `endDate` as well as the final results of the tally.
This schema is used for both PollOpen and PollClose.
A `pollUpdateTally` even will arrive upon every vote on the poll by a user.
The tick
value will increment by one for each event, and one of the option counters should increment by one due to each vote.