Castcloud is a RESTful API specifying how podcast clients can communicate a user’s podcast library to each other via a server.
The server side of the API provides centralized metadata storage for making personal podcast libraries available to multiple clients. The metadata of the library includes subscriptions, episodes, playback status and settings. The server side takes care of feed crawling so that clients don't have to individually crawl all the users feeds. This improves speed and simplifies some of the clients work.
We have made reference implementations of both the server and the client. The reference server implementation has all server side functionality and the reference client uses a synchronizing model as far as possible for modern browsers (offline media storage is currently problematic), and falling back to streaming when synchronizing is impossible.
All code is GPLv3 with some included utilities and libraries being Apache 2.0 and MIT License. This means you are free to make commercial software or solutions with our code, and in fact we encourage it!
In the spirit of making something really useful for all users of podcasting software, we hope that all developers can come together and make this a common standard and improve upon it instead of creating incompatibilities.
We are 3 bachelor computer science students from Narvik University College in Norway. We are working on this project as our bachelor thesis. We hope to have a specification ready for implementation in time for our graduation in the beginning of June 2014. We hope this API specification will help solve an issue that in our opinion has plagued and hampered further growth and user adoption of podcasts.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
A client in this documentation is a piece of software run by a user on any compatible platform. A client is identified by its name
, regardless of developer or platform. Different instances of a client is identified to the server by its UUID
or token
, and by its client name
and decription
to users.
There are 2 client models intended for use with the API. Hybrid models are also possible. Both streaming and synchronizing are equal in functionality, however it is recommended to implement as much synchronizing functionality as possible on the clients platform. Synchronizing improves the user experience as the client has a local copy of the library state. This increases the clients speed and enables offline functionality.
The primary part of a synchronizing client is making sure it has episodes
, casts
, events
, labels
and settings
stored locally. The fetching of events
and episodes
can be accelerated using a since
timestamp and correct filtering. Clients will have to remove episodes
from its library when it gets delete events
or a cast
gets removed (unsubscribed to).
To be fully offline capable, the client needs to manage the related media files locally.
Streaming clients fetch information as required. The first part consists of casts
(and labels
if the client implements folders and sorting). After letting the user select a cast
or label
, the client then fetch the related episodes
.
A server in this documentation is a piece of software running on a network capable device. It responds to REST requests as specified by this documentation. It handles permanent storage of information and information gathering from podcast feeds.
Most of the data processing is moved server side to simplify client implementation. This documentation will attempt to explain some of the required data processing to ease implementation. The explanation is located under the heading "Server logic" for each of the different calls.
It is essential that the server implements crawling correctly. All clients using the server rely on it to have fetched the required feed infomation. If this were to stop all clients would stop working. The server should be a responsible crawler, throttling and properly identifying itself with a useragent. It should implement optimizations like PubSubHubbub to be as fast as possible. It needs to identify content and give it an episode id
without causing duplication. Using the items
GUID
might mostly work, but writing a good crawler is a never ending saga.
The server will need to store all channel
information in the feed for /library/cast
and all data inside each item
for the episodes
related calls. When storing new episodes
after having crawled a feed, the servers time (API server, not the database server) should be included for when it was stored. This will be used when clients use the since
filter in the /library/newepisodes
call.
This document will attempt to explain proper usage of the API. All functions are explained in an interactive documentation called swagger. This can be viewed at http://api.castcloud.org/utils/swagger-ui/ or run on your own server instance after having compiled the swagger documentation.
To compile the swagger documentation:
cd /your/castcloud/directory/
/usr/bin/php utils/swagger-php/swagger.phar api/ -o api-docs/ --default-base-path http://your.domain.name/api
Some data returned from the server has been converted from XML to JSON. This is mostly straight forward with one exception being tags having both attributes and content inside themselves. In these cases the content inside the tag gets put in a field named “_”.
Example:
<guid isPermaLink="false">5B4B1B2C-F9C6-4DC2-B09F-B6DE889A9E25</guid>
becomes
"guid":{
"_" : "5B4B1B2C-F9C6-4DC2-B09F-B6DE889A9E25",
"isPermaLink" : "false"
}
Post: Account/Login | ||
---|---|---|
Parameter | Required | Description |
username | true | Users username |
password | true | Users password |
clientname | true | Client name |
clientdescription | true | Client Description. e.g. Sallys iPad |
clientversion | false | Client Version |
This call registers the user's client and returns the client authorization token. The authorization token is used for all other requests. UUID
, name
and description
fields are important as these are used actively.
Example:
curl https:// UrlPath /api/account/login -d username=user -d password=*** -d clientname=Superclient -d clientdescription="Bob's iPhone" -d uuid=1234567890
The UUID
is a string used to identify the client instance even if the token
is expired. The UUID
is used to reduce the number of duplicate client registrations on the server. The client can acquire a UUID
string from its platform, or just generate a random string and permanently write it to storage. The string should be at least 8 characters, preferably 256 bit / 32 characters. The string should be unique for every device, but the same for the duration of the deployment on the device.
The UUID
is the only thing that is intended to be kept after a user explicitly signs out.
The name
and description
information is used to identify the user's client. The name
should be hard coded into the client and not change with versioning or platforms (unless features vary). description
can be something less rigidly defined. It can describe the device it is running on “iPad” or “Windows 7 (x64)”, but the best description
would be something that relates to the user, for example “Livingroom media center” or “Bedroom iPad”.
When a user logs in through the API, the client calling the server provides a lot of information. clientname
, clientdescription
and uuid
are intended to simplify backend management. clientname
and clientdescription
are exposed through the API. clientversion
and apikey
are intended for future use with a possible client developer backend. The UUID
is intended to prevent duplicate client registrations. If a user has an active token
for a client with a matching UUID
and name
, clientdescription
must be updated.
This can be used to check if the token is valid.
Example:
curl http:// UrlPath /api/account/ping -H "Authorization:1337EGAh10qpLDq7xDTXG41r6T//ONLRvF5hd\/M3AX9I="
Check if the token is valid.
Clients are able to store the users settings
server side. This makes the synchronizing experience more seamless. By default all settings
are global for all clients, but clients can create clientspecific
overrides. clientspecific
overrides will apply for all clients with the same client name
. Global settings are intended for common settings
with common values
. If a client uses uncommon settings values
or doesn't understand the current setting value
, the client must override the setting
to avoid conflicts. A user might use one client differently than their other clients, and might want to configure this client separately from the rest. In these cases clients should offer the ability for users to toggle overrides of settings so that they don't propagate to other clients. When a user untoggles a setting override the client must ask if the user wants to keep the value
from the override or the global setting
.
The client has to implement its own set of supported settings
and values
with appropriate default values
. If a setting
does not exist, the client should set and use the default values. A client should not overwrite the users global settings with its defaults, unless the user asks for it. If a current settings value
is not understood by the client, the client should set its default as an override and use the override. The server side does not change its behaviour depending on settings, it only stores them. It is up to the client to implement the settings functionality.
Clients are not required to show any UI for configuring a setting. If some information about the clients state is required to save, settings
with clientspecific
overrides might be a good place to put it.
Changes to settings
should be sent to the server as fast as possible. It is recommended that clients keep an output buffer of these changes as they occur, then retry sending them until they successfully gets sent.
We hope that developers can come together and create a common list of setting keys
and values
. Therefore we suggest using these keys and values
when implementing settings for related functionality in clients:
Setting key | Values (String formatted) | |
---|---|---|
playback/forward_skip | 15s, 10% | Integer followed by a unit character. Available unit characters are "s" = seconds, "%" = percentage of the medias total duration. |
playback/backward_skip | ||
playback/speed | 1.0 | Decimal number describing the default rate of playback. 1 = normal speed. 1.5 = 150% speed |
playback/continous_playback | true | Boolean. Whether playback should continue after end of track |
gui/episode_sortorder | published, reverse published, alfa, reverse alfa | |
hotkey/playpause | spacebar,P,PlayPause | csv keyboard hotkeys |
hotkey/forward_skip | ArrowRight | |
hotkey/backward_skip | ArrowLeft | |
hotkey/next | J,N,PageDown | |
hotkey/previous | K,P,PageUp | |
sync/autodelete | false | Boolean. If true clients can auto delete podcasts with EOT events. Default `exclude` parameter for episode and event related calls should be "60,70". Notice: It is likely this might change from being a boolean to something with more details. |
Notice: Setting keys
are not a rigid part of the specification, but an attempt to find common ground. This list will be modified as per developer adoption regardless of API versioning to set a common ground.
The server should send all the users global settings
and all settings
with clientspecific
overrides for the active client. The server needs to keep track of which client specified the clientspecific
override. The clientspecific
override is applicable to all clients with the same client name string, and it is not specific to the clients UUID
or token
. Several instances of a client can keep their clientspecific
settings
in sync across several devices.
casts
is the the users subscriptions. Each cast
has its own unique cast id
. Adding a cast to the user subscription list only requires an URL
of a valid podcast feed. By using the cast id
the client can edit and delete a subscription from the library. It is also possible to import and export casts via opml.
Changes to casts
should be sent to the server as fast as possible. It is recommended that clients keep an output buffer of these changes as they occur, then retry sending them until they successfully get sent.
Example:
curl http:// UrlPath /api/library/casts -H "Authorization:SuperSecretToken"
A subscription should be stored on the server as a reference between the user and the cast. That way, if two users subscribe to the same cast they will both have the same cast id
and the episodes will have the same episode ids
. This will reduce the amount of feeds the server will need to crawl. casts.opml
are the only parts of the api that use xml. Make sure you maintain the proper label structure when you export the opml, and validate it with a validation tool. Importing to iTunes can also be a good test. When importing opml's, make sure you maintain proper label stucture. Remember that labels should only be one layer deep, so you might have to flatten it. Importing a large opml from a feedreader like bazqux.com might be a good choice. Do not forget to crawl the imported opmls.
Both these calls return somewhat similar results. What to use depends on the client model.
Example:
curl https:// UrlPath /api/library/episodes/ID -H "Authorization:SuperSecretToken"
curl https:// UrlPath /api/library/newepisodes -H "Authorization:SuperSecretToken"
If the client is using a synchronizing client model we recommend using newepisodes
and related calls as you can get episodes for all feeds with one call. In addition you can save a lot of bandwidth by using the since
parameter. When providing a since
parameter, please use the timestamp
included with the last result, and not one from the client side as these might differ.
Please note that using a synchronizing model will force you to get events from /library/events
as the lastevent
included with each episode
will quickly get outdated. If you see a new event
for an episode
that you don't have locally, this means the user has undeleted the episode
. Retrieve it with /library/episode/{episodeid}
.
If you are using a streaming model all you will need to use is /library/episodes
. Before it can be fetched, the client will need to have information from /library/casts
or /library/labels
as cast id
or label id
is a required parameter.
Deleted / hidden episodes
Users would normally keep deleting episodes as they finish listening to them. This means the episodes would disappear from the normal interface. Deleted episodes
are still stored on the server. To get the complete list of episodes
use the /library/episodes/{castid}
with "" as exclude
parameter. When a client displays the complete list of episodes
, the user should have the option to reset the playback status. Clients undelete episodes
by sending a new start event (type 10). Most synchronizing clients should implement this as an online only feature.
/library/episodes
, /library/episode
and /library/newepisodes
all return episodes
in the same format. The biggest difference are the filters defined in input parameters. /library/newepisodes
must also include a timestamp of when it was generated. The feed
in each episode
is a JSON representation of items
xml. /library/episode
only returns one episode at a time, please note that this must be output as an object and not an array.
Parameter | Use | |
---|---|---|
`since` | `/library/newepisode` | The call must only return episodes stored after this time. |
`episode id` | `/library/episode` | Required parameter, should show error if not provided. The call must only return the one episode with the specified episode id. |
`cast id` | `/library/episodes` | Required parameter, should show error if not provided. The call should only return episodes for the casts with the specified `cast id`. |
`label id` | `/library/episodes/label` | Required parameter, should show error if not provided. The call should only return episodes for casts inside the specified label. |
`exclude` | `/library/episodes` `/library/episodes/label` `/library/newepisodes` |
Parameter is comma separated integers. The call should only return `episodes` where the `episodes` `lastevent` is not one of the listed types. If no exclude parameter is provided must the server use a default filter of “70” (deleted). If the parameter is empty (“”) must no `episodes` be excluded. |
events
are how clients keeps track of the users playback progression. They relate to common actions performed on the client.
Event type | Description |
---|---|
10 | Start. Playback started. Also used to undelete/reset playback status. Almost equal to no event (`positionts` must be 0). |
20 | Pause. Playback paused. |
30 | Play. Playback resumed after a pause |
40 | Sleeptimer start. This indicates where a sleeptimer was initiated. Concurrentorder must be one less than the `concurrentorder` for the Sleeptimer end event. |
50 | Sleeptimer end. This indicates where a sleeptimer is intended to end. Should be sent with Sleeptimer start, the actual end of a sleeptimer is indicated with a pause at the end of the timer. `concurrentorder` must be one more than the `concurrentorder` for the Sleeptimer end event. |
60 | End of track. The playback has reached the end of the file and can be expected to have been listened to completely. |
70 | Delete. The user has explicitly indicated (by action) that the episode should be deleted. Clients no longer needs to maintain data about this episode unless the user explicitly asks for it. |
More complex events
are built up of combinations of events
. If a users skips from one position to another, the client should then send two events with the same ClientTS
. The first event should be a pause event
with a concurrentorder
of 0. The second event
should be a play event with the new positionts
and a concurrentorder
of 1, seeking should also be implemented this way.
Clients following a streaming model might not need to fetch events
, as the most recent event
is included when getting episodes. Some clients might offer the ability to show a list of the users events. This might better help the user find back to where they last were. Streaming model clients that offer a complete eventlist should use the cast id
to speed up the request. A synchronizing model client that does not offer this functionality, do not need to store more than the last event for each episode
.
events
should be sent to the server as fast as possible. It is recommended that clients keep an output buffer of events
as they occur, then retry sending them until they successfully get sent.
Example:
curl https:// UrlPath /api/library/events -H "Authorization:SuperSecretToken"
Parameter | |
---|---|
`since` | The call must only return events received/stored since this time. |
`episode id` | The call must only return `events` for the specified `episode`. |
`exclude` | The call must only return `events` where the `episodes` last `event` is not one of the listed `types`. If no `exclude` parameter is provided, the server must use a default filter of “70” (deleted). To completely disable the `exclude` filter submit the parameter but leave it empty (“”). |
Labels allow you to group and sort your subscriptions.
Example:
curl https:// UrlPath /api/library/labels -H "Authorization:SuperSecretToken"
The most important part of this call is making sure the output is clean and valid. Therefore the output should be validated every time before it gets sent to a client. Clients should not have to change the labels
when they unsubscribe from a podcast or subscribe to a new one. By default all casts
and labels
should be added to the root label
. A cast
does not have to be in root when it is inside another label
. You might be interested in the function clean_labels()
inside api/db.php
if you are trying to ensure clean labels
.