Overview
Welcome
There's no public API with official usage conditions etc. (yet), but you're welcome to experiment with it and give feedback.
Conventions
- The API is RESTful. UML diagram describing resources
- Calls for resources (channels, series, etc.) are mapped directly to the website. You can generally just append ".json" to a URL to view the API response in your browser.
- You can provide slugs (hyphenated names/titles that appear in the URL) OR IDs. They are interchangeable. For production use, though, clients should always use numeric IDs in case the slug (or slug generation algorithm) changes.
- Your client should be ready to follow redirects.
Associations and Nested JSON
The following info is included in the reference material further down; mentioning the general principle here as an introduction.
- "Parent" ("Has-One") associations are always included in a JSON response, and in full detail. e.g. channel always includes owner, episode always includes series.
- "Child" ("Has Many", "N-to-N") associations are never included by default. They must be requested using a "detail" parameter, as the default detail is "none". For example, to include a user's channels, use /wendy.json?channel_detail=full. These nested lists typically have three properties: detail, limit, and offset. Limit and offset are numeric. "detail" is one of "full", "minimal", "raw", "id", and "none". minimal returns a small subset of information such as title and ID. raw (only partially supported at present) returns a raw, un-keyed, data structure designed to reduce the message's size, id is only the ID. none means the list doesn't appear at all; it is the same as not including a detail parameter at all.
Performance/Caching Techniques
General-purpose clients should follow three techniques; combined, these should produce a huge improvement in speed and reduction in traffic, compared to the naieve approach. Probably a 99% or more improvement!
HTTP object freshness (ETag and Last-Modified headers)
You can make your app work much faster by caching results, and the API supports HTTP object freshness to help with that. This is what browsers do and this is what your app can do too.
For example, assume a channel JSON response includes ETag response header: "57536bf4b063b99741ac516e5" and Last-Modified header ""Fri, 16 Jan 1970 16:50:24 GMT":
ETag: "53170b9416a3d496efe6ea26f6aa5af6" Last-Modified: Thu, 27 Dec 2012 16:15:36 GMT
We should save these values as attributes on the channel record (i.e., it should have etag and last_modified columns). The next time we call this same URL, we'll pluck them from the record and pass them in on the request, in headers If-None-Match (with ETag value) and If-Modified-Since (with Last-Modified value).
If-None-Match: "53170b9416a3d496efe6ea26f6aa5af6" If-Modified-Since: Thu, 27 Dec 2012 16:15:36 GMT
We'll save these with the channel record. We should also be saving the updatedAt field in the JSON response, as it might help us prevent a call altogether.
Reducing N+1 calls with updatedAt
Sometimes you need to make "N+1" calls. In particular, you request a user to discover the user's N starred channels. In this case, the response will include an "updatedAt" field for each of the subsequent calls. By comparing this field to the previous "updatedAt" field, which should be stored on the channel, you can decide if it's actually necessary to make the call. If the values are equal, you've saved a call! This can help reduce total number of calls by 90% or more.
Improving speed of N+1 calls with latestLookup
Some JSON includes a special field called "latestLookup" for associated entities. If present, and if you are immediately requesting that associated entity, then you should use that URL instead of the regular "lookup" URL.
For example, the channels inside user json have this field). This is the URL of the latest channel version. If the client is immediately requesting this channel (because it noticed "updatedAt" has changed; see previoius point), then you should call the latestLookup URL instead of the regular channel URL provided by "lookup".
Authentication
Login (or Sign Up via Third Party)
POST
/users/sign_in.json
| JSON Key | Req? | Type | Summary |
|---|---|---|---|
| login | username, email, or ID of signing-in user. | ||
| password | string | password of signing-in user. | |
| googleOAuthAccessToken | string | OAuth 2 token (obtainable via Android OS or after web OAuth dance, etc). | |
| remember_me | boolean | extends session (always use true for now). |
Exactly one of these must be present: (a) login AND password; (b) googleOAuthAccessToken.
In the case of a the Google OAuth 2 token, this is both a login and signup operation, depending on whether user already has a Player FM account. With email/password, it's login only and the equivalent signup process is documented below.
Call example (httpie)
http -f POST player.fm/users/sign_in.json << END
{
"user": {
"login": "[email protected]",
"password": "hard2guess",
"rememberMe": true
}
}
END
Call example (httpie)
http -f POST player.fm/users/sign_in.json << END
{
"user": {
"login": "wendy",
"password": "hard2guess",
"rememberMe": true
}
}
END
Call example (httpie)
http -f POST player.fm/users/sign_in.json << END
{
"user": {
"googleOAuthAccessToken": "ya29.Ac3lMgHvwpmIq9aT2JfsSUH"
}
}
END
Successful response
If successful, the response will be a JSON object representing the user, same as performing a private user lookup call. It will have an additional key, "new", with a boolean value (i.e. true or false) indicating if the user record was created in response to this call.
Error response
HTTP/1.1 401 Unauthorized
Status: 401 Unauthorized
...
{
"errors": "Login Failed",
"success": false
}
Conventional Signup
POST
/users
| JSON Key | Req? | Type | Summary |
|---|---|---|---|
| role | string | role of new user ('tourist' or 'member'). | |
| email of new user (required if role is member). | |||
| password | string | password of new user (required if role is member). |
A "tourist" role can be used for a guest user with server-side account management. Tourist profiles are accessible only to the user, and there is no associated email or password, so no ability to sync across devices. The only authentication mechnanism is the device-tied session detail. The server makes no guarantee a tourist account will be maintained indefinitely.
After a successful response to a 'member' request, your app should ask user to confirm email. The email link will redirect them to player.fm/welcome/subscriptions, so mobile apps should ideally "hijack" that URL (e.g. using Android's intent mechanism) and start onboarding experience from there (to do so, the app will first need to login using the email/password the user originally provided).
In the event of an already-taken email, app should prompt user to login and ensure Forgot Password link is present.
Call example (httpie)
http -f POST player.fm/users.json << END
{
"user": {
"role": "tourist"
}
}
END
Call example (httpie)
http -f POST player.fm/users.json << END
{
"user": {
"email": "[email protected]",
"password": "hard2guess",
}
}
END
Successful response
If successful, the response will be a JSON object representing the public user JSON. For convenience,successful responses will always include a "new" field always set to true (consistent with the third-party signup mechanism above).
Error response
Status: 403 Forbidden
...
{
"email": "is already taken"
}
Verify user is signed in
GET
/users/signed_in[.json]
| Param | Req? | Type | Summary |
|---|---|---|---|
| :username | string | an optional username assertion. Call will fail if authenticated user is not this one. |
Request Headers
Call example (httpie)
[deprecated] http GET player.fm/users/signed_in.json Cookie:pfm-session=bnNpb25zKS4gVGhlIHNjaGVtZSB3b3JrcyBvbiA4IGJpdCBkYXRhLiBUaGUgcmVzdWx0YW50IGJhc2U2NC1lbmNvZGVkIGRhdGEgaGFzIGEgbGVuZ3RoIHRoYXQgaXMgZ3JlYXRlciB0aGFuIHRoZSBvcmlnaW5hbCBsZW5ndGggYnkgdGhlIHJhdGlvIDQ6My4KVGhyZWUgYnl0ZXMgYXJlIGNvbmNhdGVuYXRlZCwgdGhlbiBzcGxpdCB0byBmb3JtIDQgZ3JvdXBzIG9mIDYtYml0cyBlYWNoOyBhbmQgZWFjaCA2LWJpdHMgZ2V0cyB0cmFuc2xhdGVkIHRvIGFuIGVuY29kZWQgcHJpbnRhYmxlIEFTQ0lJIGNoYXJhY3RlciwgdmlhIGEgdGFibA; http GET player.fm/users/signed_in.json X-AuthToken:"123:fd560f85…" http GET player.fm/users/signed_in.json?authToken="123:fd560f85…"
Successful response
HTTP/1.1 200 OK
[deprecated] Set-Cookie: pfm-session=bnNp....(same as cookie above)...
Status: 200 OK
...
{
"success": true,
"username": "wendy"
"auth_token": "123:fd560f85…"
}
Error response
HTTP/1.1 403 Forbidden
[deprecated] Set-Cookie: pfm-session=bnNp....(same as cookie above)...
Status: 403 Forbidden
...
{
"success": false
}
Memberships and Plans
Create membership (in-app subscription)
POST
example
- "Membership" here refers to a single in-app subscription (the term "subscription" was not used to avoid confusion with podcast subscriptions).
- A single user may have multiple memberships, ie if one membership expired or was cancelled and another one created later. The server does nothing to prevent overlapping memberships, beyond triggering an internal warning. For example, if a user wanted to upgrade to a higher level membership, the lower one would have to be manually cancelled or refunded at present.
- After calling this, a membership object is created and verification attempted. The resulting membership resource is returned. The app should poll server later in the event the call was not yet verified (verifiedAt timestamp will be empty in that case).
- Post body should be a JSON hash keyed on "membership", which has a nested hash containing information about the transaction that occured on the device. Refer to example below
- (For Google) Prior to request payment, app should generate a random string and provide this as the "developer's payload". The same string should be uploaded in post body as "clientsPayload".
Call example (httpie)
http POST player.fm/memberships content-type:application/json X-Auth-Token:"123:fd560f85…" << END
{
membership: {
"userID": 123,
"planID": "gold_plan_yearly",
"membershipType": "google",
"providersToken": "abcdefghi123456789zzz"
"clientsPayload": "randomguid1234567890"
}
}
END
Resources
Look up resource (general-purpose)
example 1 example 2
GET
?url=:url
| Param | Req? | Type | Summary |
|---|---|---|---|
| :url | URL | A valid Player FM website address identifying the resource. |
- This is a general-purpose lookup, primarily intended for situations where the client has a Player FM website URL and needs to map it into a resource of arbitrary type. For example, if another app is trying to launch a Player FM URL in the Player FM app.
- Presently supports users, channels, series, and episodes.
- Remember to escape the url parameter.
- The call will return zero or one resource and the response body will be identical to an individual lookup on the same resource, i.e. http://player.fm/resources.json?url=http%3A//player.fm/series/123 is guaranteed to have precisely the same response as if the client had requested http://player.fm/series/123.json. Any permissioning/privacy decisions will be resolved the same way too.
- Short IDs such as /1ab3zh are also supported. You can inspect the response's type field to determine how to render it, and you should ideally be prepared for type to be any one of user, channel, series, episodes, as well as gracefully handling any other types that might be introduced later. If your application cannot render a type, you may invite the user to open the URL in the browser, presenting them with the expanded URL which will be found in the response's lookup field.
Users
Look up user
example 1 example 2 example 3 example 4
GET
/:id[/private].json
| Param | Req? | Type | Summary |
|---|---|---|---|
| :id | int/string | ID (or username) of user. | |
| :fixed_channels | discover,play-later | Comma-separated list of channels that will be appended to user's channels list. discover and play-later are those supported now. | |
| channel_detail | One of: full, detail, minimal, none | How much info should be shown about child channel, if any. | |
| channel_limit | int | How many channels to retrieve (max 50, default 50). | |
| channel_offset | int | Offset to support paging through all channels (default 0). | |
| favorite_detail | One of: full, detail, minimal, none | How much info should be shown about child favorite, if any. | |
| favorite_limit | int | How many favorites to retrieve (max 50, default 50). | |
| favorite_offset | int | Offset to support paging through all favorites (default 0). | |
| subscribed_series_detail | One of: full, detail, minimal, none | How much info should be shown about child subscribed_series, if any. | |
| subscribed_series_limit | int | How many subscribed_series to retrieve (max 50, default 50). | |
| subscribed_series_offset | int | Offset to support paging through all subscribed_series (default 0). | |
| series_setting_detail | One of: full, detail, minimal, none | How much info should be shown about child series_setting, if any. | |
| series_setting_limit | int | How many series_settings to retrieve (max 50, default 50). | |
| series_setting_offset | int | Offset to support paging through all series_settings (default 0). | |
| series_setting_updated_since | int | restrict to models updated after a certain time (in milliseconds since 1970 epoch). | |
| theme_detail | One of: full, detail, minimal, none | How much info should be shown about child theme, if any. | |
| theme_limit | int | How many themes to retrieve (max 50, default 50). | |
| theme_offset | int | Offset to support paging through all themes (default 0). | |
| theme_updated_since | int | restrict to models updated after a certain time (in milliseconds since 1970 epoch). | |
| setting_detail | One of: full, detail, minimal, none | How much info should be shown about child setting, if any. | |
| setting_limit | int | How many settings to retrieve (max 50, default 50). | |
| setting_offset | int | Offset to support paging through all settings (default 0). | |
| setting_updated_since | int | restrict to models updated after a certain time (in milliseconds since 1970 epoch). | |
One of: full, detail, minimal, none | Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites | ||
Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites | |||
Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites |
Update user
PUT
/:id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :id | int/string | ID (or username) of user. |
| JSON Key | Req? | Type | Summary |
|---|---|---|---|
| name | string | Player FM Username. | |
| fullName | string | Users full name in free-form, e.g. "John Malkovich". | |
| access | One of: public, private | Global default access level for this user. This variable does not directly affect any access decisions, but is used as the default for finer-grained access settings. | |
| favorites_access | One of: public, private, null | Access level of users' favorite channels. A null value will cause it to delegate to the user's access property. | |
| reset_all_access | One of: true, false | Nullify all of this user's fine-grained access settings (so they will all inherit from access). Specifically, favorites_access and all of the users' channel access values. Setting this in conjunction with a value for access is a convenient way to provide users with a simple on/off privacy setting for all their data. |
Request Headers
Content-Type must be present and set to application/jsonCall example (httpie)
http -f PUT http://localhost:3000/123/456 Content-Type:application/json << END
{
"user": {
"name": "wendy",
"fullName": "Wendy Wonka",
"email": "[email protected]",
"access": "private",
"mailDigest": true
}
}
END
Channels, Subscriptions, Selections, and Favoritng
Look up channel
example 1 example 2
GET
/[:language/]:user_id/:channel_id[/at/:timestamp].json
| Param | Req? | Type | Summary |
|---|---|---|---|
| :language | 2-character language code (e.g. en) | If present, series and episodes will be filtered on this language. In the future, the response might also included the title and description in their translated forms. | |
| :user_id | int/string | ID (or username) of channel owner. | |
| :channel_id | int/string | ID (or slug) of channel. Note that channel IDs are unique across all of Player FM, while channel slugs are only guaranteed unique for each user. | |
| :timestamp | int | Timestamp (corresponding to updatedAt field) of last known update. This is optional and should only be used if the timestamp data has just been received in a previous call, as a performance optimisation. | |
| episode_detail | One of: full, detail, minimal, none | How much info should be shown about child episode, if any. | |
| episode_limit | int | How many episodes to retrieve (max 50, default 50). | |
| episode_offset | int | Offset to support paging through all episodes (default 0). | |
| episode_order | One of: rank, reverse-rank, newest, oldest, longest, shortest, series, trend1, trend28, trend365, random | Episode sort order (default newest). | |
| series_detail | One of: full, detail, minimal, none | How much info should be shown about child series, if any. | |
| series_limit | int | How many series to retrieve (max 50, default 50). | |
| series_offset | int | Offset to support paging through all series (default 0). | |
| series_order | One of: alphabetical, reverse-alphabetical, subscribed, reverse-subscribed, updated, reverse-updated, trending, reverse-trending, popular, subscriptions, reverse-subscriptions, fetched, reverse-fetched, earliest, reverse-earliest, created, reverse-created, archived, reverse-archived | Series sort order (default updated). |
Update channel
PUT
/:user_id/:channel_id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :user_id | int/string | ID (or username) of channel owner. | |
| :channel_id | int/string | ID (or slug) of channel. |
| JSON Key | Req? | Type | Summary |
|---|---|---|---|
| access | One of: public, private | Channel access level. |
Request Headers
Content-Type must be present and set to application/jsonCall example (httpie)
http -f PUT http://localhost:3000/123/456 Content-Type:application/json "<< END"
{
"channel": {
"access": "private"
}
}
END
Favorite a channel
POST
/favorites?userID=:user_id&channelID=:channel_id[&language=:language]
| Param | Req? | Type | Summary |
|---|---|---|---|
| :user_id | int/string | ID (or username) of user favoriting the channel. | |
| :channel_id | int/string | ID (or slug) of channel to be favorited. | |
| :language | 2-character language code (e.g. en) | If present, the new favorite will apply only to language-specific content of the specified channel. If absent, the favorite acts as a "wildcard", ie it matches on all of this channel's languages (in practice, featured channels should always be favorited with a language included). |
Call example (httpie)
http POST player.fm/favorites?userID=1&channelID=99 Content-Type:application/json X-Auth-Token:"123:fd560f85…"
Successful response
{
"user": { ... }
"channel": { ... }
"language": 'en'
}
Un-favorite a channel
DELETE
/favorites?userID=:user_id&channelID=:channel_id[&language=:language]
| Param | Req? | Type | Summary |
|---|---|---|---|
| :user_id | int/string | ID (or username) of user un-favoriting the channel. Must be authenticated user. | |
| :channel_id | int/string | ID (or slug) of channel to be un-favorited. | |
| :language | 2-character language code (e.g. en) | The language of the channel to be un-favorited. Note that leaving this blank would only remove the "wildcard" favorite on this channel, if it exists; it would *not* serve to magically delete all favorites on the channel in any language. In other words, this request will always delete one favorite, at most. |
Call example (httpie)
http DELETE player.fm/favorites?userID=1&channelID=99 Content-Type:application/json X-Auth-Token:"123:fd560f85…"
Successful response
The result is a list of the user's currently starred channel IDs.
subscribe (add series to a subscription channel)
POST
/subscriptions?channelid=:channel_id&seriesID=:series_id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :channel_id | int/string | id (or username) of channel. Must be owned by authenticated user. | |
| :series_id | int/string | id (or slug) of series. |
Call example (httpie)
http POST player.fm/subscriptions?channelID=1&seriesID=99 content-type:application/json X-Auth-Token:"123:fd560f85…"
un-subscribe (remove series from a subscription channel)
DELETE
/subscriptions?channelID=:channel_id&seriesID=:channel_id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :channel_id | int/string | id (or username) of channel. Must be owned by authenticated user. | |
| :series_id | int/string | id (or slug) of series. |
Call example (httpie)
http DELETE player.fm/subscriptions?channelID=1&seriesID=99 content-type:application/json X-Auth-Token:"123:fd560f85…"
select (add episode to a playlist channel)
POST
/selections
| Param | Req? | Type | Summary |
|---|---|---|---|
| :channel_id | int/string | id (or username) of channel. Must be owned by authenticated user. | |
| :episode_id | int/string | id (or slug) of episode. | |
| :rank | long integer (64-bit) | selection rank (defaults to milliseconds since 1970 epoch). | |
| :segments | string | JSON-serialized list of zero or more segments, each segment has mandatory "start" (int) and optional "finish" (int) and "comment" (string) fields. |
Call example (httpie)
http POST player.fm/selections content-type:application/json X-Auth-Token:"123:fd560f85…" << END
{
selection: {
channelID: 123,
episodeID: 456,
rank: 1439202010984
}
}
END
Call example (httpie)
http POST player.fm/selections content-type:application/json X-Auth-Token:"123:fd560f85…" << END
{
selection: {
channelID: 123,
episodeID: 456,
segments: [ { "start": 123 }, { "start": 123, finish: 456, comment: "Funny" } ]
}
}
END
update selection
PUT
/selections/:id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :id | int | id of selection. |
Call example (httpie)
http PUT player.fm/selections content-type:application/json X-Auth-Token:"123:fd560f85…" << END
{
selection: {
rank: 1439202010984
}
}
END
un-select (remove selection from a channel)
DELETE
/selections/:id
| Param | Req? | Type | Summary |
|---|---|---|---|
| :id | int | id of selection. |
Call example (httpie)
http DELETE player.fm/selections?selectionID=99 content-type:application/json X-Auth-Token:"123:fd560f85…"
Series and Feeds
Look up a series
example 1 example 2
GET
/series/:id[/at/:timestamp].json
| Param | Req? | Type | Summary |
|---|---|---|---|
| :id | int/string | ID (or slug) of series. | |
| :timestamp | int | Timestamp (corresponding to updatedAt field) of last known update. This is optional and should only be used if the timestamp data has just been received in a previous call, as a performance optimisation. | |
| episode_detail | One of: full, detail, minimal, none | How much info should be shown about child episode, if any. | |
| episode_limit | int | How many episodes to retrieve (max 50, default 50). | |
| episode_offset | int | Offset to support paging through all episodes (default 0). | |
| episode_order | One of: rank, reverse-rank, newest, oldest, longest, shortest, series, trend1, trend28, trend365, random | Episode sort order (default newest). |
Look up feeds (with OPML support)
POST
/feeds
Request Headers
Content-Type: application/jsonCall example (httpie)
http POST player.fm/feeds content-type:application/json << END
{
"opml": " "
}
END
RESTful response
"feeds": [
{
"url":"http://leoville.tv/podcasts/twit.xml",
"title":"this WEEK in TECH",
"home":"http://twit.tv",
"series":{
"type":"series",
"id":241,
"slug":"this-week-in-tech-mp3-edition",
"home":"http://thisWEEKinTECH.com",
"url":"http://leoville.tv/podcasts/twit.xml",
"author":"TWiT",
"updatedAt":1356626502,
"title":"this WEEK in TECH - MP3 Edition",
"description":"Your first podcast of the week is the last word in tech. Join Leo Laporte, Patrick Norton, Kevin Rose, John C. Dvorak, and other tech luminaries in a roundtable discussion of the latest trends in high tech. Winner of People's Choice Podcast and Best Technology Podcast in the 2005 People's Choice Podcast Awards. Released every Sunday at midnight Pacific.",
"imageURL":"http://leoville.tv/podcasts/coverart/twit600audio.jpg",
"fetchStatus":"ok",
"lookup":"http://localhost:3000/series/241.json",
"latestLookup":"http://localhost:3000/series/241/at/1356626502.json",
"latestVerboseLookup":"http://localhost:3000/series/241/at/1356626502.json?episode_detail=full",
"share":"http://localhost:3000/series/this-week-in-tech-mp3-edition",
"network":{
"name":"Leo Laporte"
},
"stats":{
"numberOfSubscriptions":3,
"numberOfEpisodes":10,
"averageDuration":7453,
"averageInterval":543771,
"earliestPublishedAt":1350881797,
"latestPublishedAt":1356319504
}
}
},
{
"url":"httpmalformed",
"title":null,
"home":null
},
{
"url":"http://noluck.example.com",
"title":null,
"home":null
}
]
- "Feeds" per se aren't stored by Player FM. Only series are stored, containing feeds as attributes. This call extracts feeds from the OPML, returns the attributes specified in the OPML, and also nests the series for each feed if it's indexed by Player FM. Note that the "title" and "home" attributes of the feed are those specified in the OPML, and might not necessarily correspond to those stored by Player FM (which represent the actual title and home specified in the current feed, modulo any user-generated overrides).
- Despite being read-only, POST is expected so that OPML can be conveniently included in the body instead of on the URL.
Create a series
POST
/series
Request Headers
Content-Type: application/jsonCall example (httpie)
http POST player.fm/series content-type:application/json << END
{ "series": { "url" : "http://leoville.tv/podcasts/twit.xml" } }
END
- new - No attempt has been made to fetch the feed yet
- ok - No attempt has been made to fetch the feed yet
- invalid - No attempt has been made to fetch the feed yet
- stale - Was previous ok, but most recent fetch failed
Episodes
Look up episode
example
GET
/series/:series_id/:episode_id.json
| Param | Req? | Type | Summary |
|---|---|---|---|
| :series_id | int/string | ID (or slug) of series this episode belongs to. | |
| :episode_id | int/string | ID (or slug) of episode. Note that episode IDs are unique across all of Player FM, while episode slugs are only guaranteed unique for each series. |
Batch Calling
Batch - call several REST calls at once
POST
/batch
RESTful response
The result should always be "207 Multi-Status". (For now, it might also be 200 too.) The body will contain the same list that was passed in, but each list item additionally includes a new integer field, status, indicating the result of the call. Each of these items also includes an optional "errors" field, which is a hash mapping string attributes to error messages.
Call example (httpie)
http POST player.fm/batch Content-Type:application/json X-Auth-Token:"123:fd560f85…" << END
[
{ "method":"post", "path":"/favorites?userID=1&channelID=1" },
{ "method":"delete", "path":"/favorites?userID=1&channelID=2" },
{ "method":"post", "path":"/selections?channelID=1&episodeID=2" },
{ "method":"put", "path":"/selections?123&selection[rank]=1439202010984" }
]
END
Importing
Import existing or new series
example
POST
/importer/import.json
The post body should be a JSON object with a single field called "wanted", whose value is a string. This string may either be a list of URLs or an OPML string. The response is always a search channel URL (as "lookup": url).
The search channel uses the "series:id1,id2..." slug format to return a list of identified series, ie those that were imported.
Clients should render this using standard "offset" style pagination and may also offer ability to refresh if there are un-fetched series present, as indicated by the individual series' "fetch" status.
Call example (httpie)
http POST player.fm/importer/import.json Content-Type:application/json << END
{
wanted: "http://example.com/a1.rss\nhttp://example2.rss"
}
END
Series settings
Create theme
example
POST
/:user/series_settings/:series_id.json
Call example (httpie)
http POST player.fm/michael/series_settings.json Content-Type:application/json X-Auth-Token:"123:fd560f85…" << END
{
speed: 1.5,
downloadOrder: 'oldest_unplayed'
}
Settings
Create or update setting
example
POST
/:user/settings.json
Call example (httpie)
http POST player.fm/michael/settings/value_play_speed_user_preferred.json Content-Type:application/json X-Auth-Token:"123:fd560f85…" << END
{
value: 2.1,
updated_at: 1519319918
}
Themes
Create theme
example
POST
/:user/themes.json
Call example (httpie)
http POST player.fm/michael/themes.json Content-Type:application/json X-Auth-Token:"123:fd560f85…" << END
{
primaryColor: 'abc123',
backgroundColor: 'def456',
textColor: '$palette'
}
Update theme
example
POST
/:user/themes/123.json
Call example (httpie)
http POST player.fm/michael/themes/123.json Content-Type:application/json X-Auth-Token:"123:fd560f85…" << END
{
primaryColor: 'abc123',
backgroundColor: 'def456',
textColor: '$palette'
}
Searching
Search is a channel owned by "podcasts" user
Search for series, episodes, channels
example
example
GET
/podcasts/:query.json
Recommendations
Resources
Resources - Meta API mapping official URLs to resources
example 1 example 2 example 3
GET
/resources?url=:url
RESTful response
The result is simply the mapped resource as JSON. It's exactly the same payload you would get if you had directly called the resource using the GET methods documented above. It works for channels, series, and episodes.
As well as accepting Player FM URLs, this API supports mapping from feed URLs to series resources. It accepts feed:, pcast:, itms, and several other itms variants. With each of these, it will accept several variants, e.g. in the case of feed, it will accept feed:http://example.com, feed://https://example.com, and feed:example.com (the last item will be attempted to match with both http and https, since the underlying protocol is ambiguous).
Error response
404 status code
Call example (httpie)
# Get JSON for series ID 10 (using web address) http GET "player.fm/resources.json?url=https%3A%2F%2Fplayer.fm%2Fseries%2F10" # Get JSON for series ID 10 (using .json API format, same result as previous) http GET "player.fm/resources.json?url=https%3A%2F%2Fplayer.fm%2Fseries%2F10.json" # Get JSON for series with identified feed URL http GET "player.fm/resources.json?url=feed:http%3A%2F%2Ffeeds.feedburner.com%2Fminterdialogueradioshow"
Suggestions
Look up personalised suggestions (experimental)
example
GET
/:user_id/suggestions.json?type=series[&limit=:limit]
| Param | Req? | Type | Summary |
|---|---|---|---|
| :user_id | int/string | ID (or username) of user un-starring the channel. Must be authenticated user. | |
| :limit | int | Number of suggestions required (default: 10, max: 50). |
Languages
Look up reference languages
example
GET
/languages.json[?with_multi=true]
| Param | Req? | Type | Summary |
|---|---|---|---|
| :with_multi | boolean | Prepend multi-language value ("mu"). |
Appendix
Appendix: Double Enhanced URI Encoding
slugs should be "double-enhanced URI encoded This sounds complicated, but is actually fairly simple to implement - here is a CoffeeScript implementation:
extendedURIEncode = (s) ->
encodeURIComponent(s).replace(/\./g, '%2E').replace(/\//g, '%2F')
doubleURIEncode = (s) ->
extendedURIEncode(U.extendedURIEncode(s))
Any major programming language will have a library for this and it will convert certain characters The "extended" encoding just ensures that slashes and dots are encoded.
That's all you need to know about this; read on below if you want to understand why this is necessary.
This is necessary because Player FM's API and URL scheme is fairly ambitious. We want To ensure URLs are flexible, support response types (e.g. ending in .json or .rss), and work as standard addresses that can be typed into a web browser (particularly for search), .
Unfortunately, web browsers automatically convert percent encodings to the underlying characters in most cases. e.g. if you enter %2E into Chrome, it will not send that. It will send the underlying character, ".". This lead to ambiguity, e.g. is the dot our requested URL format (blahblah.json) or is it part of the slug (searching for something ending in .json)?. There's no way to coerce Chrome into sending the percent-encoding, so there's no way for the server to distinguish the caller's intent.
To get around this, we require clients to double-encode on the client and the server will then double-decode. We require this encoding to be "enhanced" because encoding of slashes and dots is handled inconsistently across libraries. So make sure to use your own encoding wrapper function which encodes the slash and dot.