▶  Expand all sections
▼  Collapse all sections

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 KeyReq?TypeSummary
loginemailusername, email, or ID of signing-in user.
passwordstringpassword of signing-in user.
googleOAuthAccessTokenstringOAuth 2 token (obtainable via Android OS or after web OAuth dance, etc).
remember_mebooleanextends 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 KeyReq?TypeSummary
rolestringrole of new user ('tourist' or 'member').
emailemailemail of new user (required if role is member).
passwordstringpassword 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]
ParamReq?TypeSummary
:usernamestringan optional username assertion. Call will fail if authenticated user is not this one.
Request Headers
[Deprecated] As with all authenticated methods, you must pass a "Cookie" header with value "pfm-session=bnNpb25zKS4gVG…" (the part after the equals sign is what login returned).
As with all authenticated methods, you must pass an auth token header or param.
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
ParamReq?TypeSummary
:urlURLA 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
ParamReq?TypeSummary
:idint/stringID (or username) of user.
:fixed_channelsdiscover,play-laterComma-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_limitintHow many channels to retrieve (max 50, default 50).
channel_offsetintOffset 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_limitintHow many favorites to retrieve (max 50, default 50).
favorite_offsetintOffset 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_limitintHow many subscribed_series to retrieve (max 50, default 50).
subscribed_series_offsetintOffset 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_limitintHow many series_settings to retrieve (max 50, default 50).
series_setting_offsetintOffset to support paging through all series_settings (default 0).
series_setting_updated_sinceintrestrict 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_limitintHow many themes to retrieve (max 50, default 50).
theme_offsetintOffset to support paging through all themes (default 0).
theme_updated_sinceintrestrict 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_limitintHow many settings to retrieve (max 50, default 50).
setting_offsetintOffset to support paging through all settings (default 0).
setting_updated_sinceintrestrict to models updated after a certain time (in milliseconds since 1970 epoch).
starred_channel_detail
One of:
full, detail, minimal, none
How much info should be shown about child starred_channel, if any.
Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites
starred_channel_limitintHow many starred_channels to retrieve (max 50, default 50).
Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites
starred_channel_offsetintOffset to support paging through all starred_channels (default 0).
Deprecated - Use "favorite" instead now, as it will support the new model of finely-grained favorites
The "/private" suffix (e.g. /14/private.json) only works if the authenticated user is querying themeselves (ID of authenticated user must match :id). It will provide information such as channels marked private. You must use this to get private information as the API will not redirect or include it if you call the non-private URL.
Update user
PUT
/:id
ParamReq?TypeSummary
:idint/stringID (or username) of user.
JSON KeyReq?TypeSummary
namestringPlayer FM Username.
fullNamestringUsers 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.
Response Codes: 200, 400
Request Headers
Content-Type must be present and set to application/json
Call 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
ParamReq?TypeSummary
:language2-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_idint/stringID (or username) of channel owner.
:channel_idint/stringID (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.
:timestampintTimestamp (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_limitintHow many episodes to retrieve (max 50, default 50).
episode_offsetintOffset 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_limitintHow many series to retrieve (max 50, default 50).
series_offsetintOffset 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).
Response Codes: 200, 500
For production use, the owner and channel ID should normally be numeric IDs, e.g. /3/582.json. However, in the case of dynamic channels, they should both be slugs (since there is no numeric channel ID), e.g. /podcasts/foo.json. Furthermore, the slug should be double URI encoded.
Update channel
PUT
/:user_id/:channel_id
ParamReq?TypeSummary
:user_idint/stringID (or username) of channel owner.
:channel_idint/stringID (or slug) of channel.
JSON KeyReq?TypeSummary
access
One of:
public, private
Channel access level.
Response Codes: 200, 403, 500
Request Headers
Content-Type must be present and set to application/json
Call 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]
ParamReq?TypeSummary
:user_idint/stringID (or username) of user favoriting the channel.
:channel_idint/stringID (or slug) of channel to be favorited.
:language2-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…"
The result is the newly created favorite
Successful response
{
"user": { ... }
"channel": { ... }
"language": 'en'
}
Un-favorite a channel
DELETE
/favorites?userID=:user_id&channelID=:channel_id[&language=:language]
ParamReq?TypeSummary
:user_idint/stringID (or username) of user un-favoriting the channel. Must be authenticated user.
:channel_idint/stringID (or slug) of channel to be un-favorited.
:language2-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
ParamReq?TypeSummary
:channel_idint/stringid (or username) of channel. Must be owned by authenticated user.
:series_idint/stringid (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…"
The channel must be owned by the user, and presently Player FM only allows regular users to own a single channel, called their prime channel. So you would generally want to create subscriptions with your user's prime channel, i.e., the channelID parameter would be the ID of the user's prime channel. This can be discovered at /.json.
un-subscribe (remove series from a subscription channel)
DELETE
/subscriptions?channelID=:channel_id&seriesID=:channel_id
ParamReq?TypeSummary
:channel_idint/stringid (or username) of channel. Must be owned by authenticated user.
:series_idint/stringid (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
ParamReq?TypeSummary
:channel_idint/stringid (or username) of channel. Must be owned by authenticated user.
:episode_idint/stringid (or slug) of episode.
:ranklong integer (64-bit)selection rank (defaults to milliseconds since 1970 epoch).
:segmentsstringJSON-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
The call is analogous to subscriptions, but the API style has been updated to support nested params. Eventually, subscriptions and others will follow suit. It's also possible to nest params on the URL, ie selections?selection[channelID]=123&selection[episodeID]=456. As with selections, a channel may only select any episode once, ie {channel-episode} pair is guaranteed unique and may be used as a secondary key to uniquely identify a selection.
update selection
PUT
/selections/:id
ParamReq?TypeSummary
:idintid 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
ParamReq?TypeSummary
:idintid 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
ParamReq?TypeSummary
:idint/stringID (or slug) of series.
:timestampintTimestamp (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_limitintHow many episodes to retrieve (max 50, default 50).
episode_offsetintOffset 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).
Response Codes: 200, 500
Look up feeds (with OPML support)
POST
/feeds
Request Headers
Content-Type: application/json
Call 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/json
Call example (httpie)
http POST player.fm/series content-type:application/json << END
  { "series": { "url" : "http://leoville.tv/podcasts/twit.xml" } }
END
The Series JSON includes a fetchStatus field, which may be one of:
  • 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
After creation, the series will be response's fetchStatus field will be new and added to the background-processing list. This means the immediate response will not take into account what's actually on the specified feed URL. For this reason, it's recommended that clients poll the series at 10 second intervals, inspecting the fetch status to check if it has progressed beyond new.

Episodes

Look up episode example
GET
/series/:series_id/:episode_id.json
ParamReq?TypeSummary
:series_idint/stringID (or slug) of series this episode belongs to.
:episode_idint/stringID (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.
Response Codes: 200, 500

Batch Calling

Batch - call several REST calls at once
POST
/batch
The post body should be a JSON-encoded list. Each list item represents a call, having fields "method" and "path" (see example).
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
Requires premium 'settings' permission. User must be subscribed to this series.
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
Requires premium 'settings' permission
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
Requires premium 'themes' permission. Use $palette, $series, $primary to point to other properties (TBA).
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
Requires premium permission. Use $palette, $series, $primary to point to other properties (TBA).
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
This is a dynamic channel. Any channel "owned" by user "podcasts" is a dynamic search channel. The query may be a title, topic, or URL. URLs will match against the series feed URL and homepage URL. The "query" is a standard channel slug, and as such, should be double-encoded.

Recommendations

Resources

Resources - Meta API mapping official URLs to resources example 1 example 2 example 3
GET
/resources?url=:url
The URL should be percent-encoded, e.g. use %2F instead of /.
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]
ParamReq?TypeSummary
:user_idint/stringID (or username) of user un-starring the channel. Must be authenticated user.
:limitintNumber of suggestions required (default: 10, max: 50).
Response Codes: 200, 403
This API is presently intended for new users, so it doesn't take into account existing subscriptions and there's no way to retrieve further suggestions. It's also limited to series. The algorithm is mainly based on starred channels, but will fill up with popular series if more series are needed.

Languages

Look up reference languages example
GET
/languages.json[?with_multi=true]
ParamReq?TypeSummary
:with_multibooleanPrepend multi-language value ("mu").
Response Codes: 200
This API is intended to be used at compile-time, to get the full list of supported API languages.

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.

Quick Reference Guide

Copyright 2025 | Privacy Policy | Terms of Service | | Copyright
Listen to this show while you explore
Play