Feed Media Group Streaming API (v3)
The https://feed.fm/api/v3 surface is the public, customer-facing Feed.fm streaming API.
It lets an application start a session, list the stations it is licensed to
play, reserve a song, stream it, and report playback back so that royalties
are accounted for correctly.
Public versus internal
Everything in this document is public and supported. Anything not in this document is internal, unsupported, and may change or disappear without notice.
Fetching this document
The server serves this document at http://feed.fm/api/v3/openapi.yaml, as
application/yaml. That route is the one endpoint on the surface that takes
no credentials, so a docs renderer or code generator can be pointed straight
at it. It is not listed as an operation below: it delivers this contract
rather than being part of it.
Authentication
Every /api/v3 endpoint requires credentials. There is no anonymous
access, and requests must be made over HTTPS.
There is one scheme — HTTP Basic (basicAuth), with your Feed.fm token as
the username and your secret as the password — and two headers it may
arrive in. The value is identical in both:
Authorization: Basic base64(token:secret)— the standard header. Use this wherever you can set it.X-Authorization: Basic base64(token:secret)— the same value under a different name, for browser and SDK environments that cannot setAuthorization.
Send one or the other, not both: X-Authorization is applied first and
replaces any Authorization header on the same request.
Only basicAuth appears under securitySchemes below, because OpenAPI
cannot express one scheme arriving under a second header name. The
X-Authorization form is equally supported.
The token may be either your long-lived consumer token or a short-lived
access token obtained from POST /access_token. Creating or revoking an
access token itself requires consumer credentials.
Do not ship the consumer secret to end-user devices or browsers. Mint an access token on your own server and hand the device that pair instead.
A request with missing or invalid credentials is answered with HTTP 401,
the header WWW-Authenticate: Basic realm="Feed.fm", and a badCredentials
error body.
Client identification
Most endpoints act on a client — one installation or listener of your application. Every endpoint that takes a client accepts it in the same three ways, in this order of precedence:
client_idin the request body- a
cidcookie client_idin the query string
All three are read on every such endpoint, whatever its method: a body
property on a GET, or a query parameter on a POST, works exactly as
well. This is why client_id is not marked required in any request body or
query schema in this document — it is required, but any one of the three
carriers satisfies it. Supply it one way or another.
Values longer than 23 characters are silently truncated. POST /session is
the only endpoint that will mint a client id for you when none is supplied;
everywhere else a missing client id is a missingParameter (code 16)
error.
While a client_id value may be shared between devices, those devices must
not request or play music simultaneously.
Persist the client_id across app restarts, so the listener's play history
and skip limits carry over.
Response envelope
Every response is JSON with a top-level success boolean. Successful calls
add endpoint-specific keys alongside it. Failures look like:
{
"success": false,
"error": { "code": 16, "message": "Missing client id", "status": 400 }
}
error.code is the stable, machine-readable value — branch on it, not on
error.message, which is free text that varies by call site.
Error codes
| code | mnemonic | HTTP | meaning |
|---|---|---|---|
| 5 | badCredentials | 401 | Credentials missing or invalid. |
| 6 | forbidden | 403 | The credentials are valid but may not do this. |
| 7 | skipDenied | 200 | The listener has no skips available. |
| 9 | noMoreMusic | 200 | No song can be played right now for this client and station. |
| 12 | playNotActive | 200 | The play is not currently being played. |
| 15 | invalidParameter | 400 | A parameter was present but malformed. |
| 16 | missingParameter | 400 | A required parameter was absent. |
| 17 | missingObject | 404 | No such play, station, placement or token. |
| 18 | internalError | 500 | Something went wrong on our side. |
| 19 | noMusic / notUS | 403 | No music is available for this client. |
| 20 | playbackStarted | 403 | This play was already started or invalidated. |
| 21 | playbackComplete | 403 | This play was already skipped or invalidated. |
| 22 | throttled | 429 | Too many errors from this client; slow down. |
| 23 | notOnDemandOrReplay | 403 | This station does not support on-demand playback. |
| 24 | formatUnavailable | 200 | No transcode matches the requested format and bitrate. |
A trap worth reading twice
Some errors arrive with HTTP 200. Codes 7, 9, 12 and 24 have no HTTP
status of their own and are returned with a 200. Always check success
before trusting a 2xx.
Other behaviour
force200— passingforce200in the query string or body suppresses the HTTP error status so that failures arrive as HTTP 200 with the real status inerror.status. It exists for environments that cannot read non-2xx bodies. It is deliberately not listed as a parameter on each operation.- Throttling — a client that accumulates errors is throttled on
POST /playwith athrottled(22) error and HTTP 429.noMoreMusic(9) answers fromPOST /playare counted as errors. A client that collects 10 of them within 60 seconds of the first is throttled until 5 minutes after that first one. A client that keeps callingPOST /playon a station that has run out of music is the usual cause. - CORS — all responses, including errors, carry
Access-Control-Allow-Origin: *. - Method override — clients that cannot issue
DELETEmay POST with anX-HTTP-Method-Override: DELETEheader. The header is read before routing, so the request is dispatched to theDELETEoperation and thePOSTone never runs. On/play/{play_id}/like, where both verbs exist and mean opposite things, that changes what the call does: a POST carrying the header removes the like instead of recording it. - Unknown paths under
/api/v3produce a plain HTML 404, not the JSON error envelope.
Organization
All music is played in the context of a station. Stations may be
discovered via POST /session (which returns a subset of available station
at that point in time), POST /station (which searches for a station and
calls POST /play on it), or GET /station (which paginates through all
available stations). A station should be identified by its unique uuid, as
all the other properties of the station may change. A station's id
property identifies a version of the station at some point in time, and that
value should not be persisted by clients outside of a single music listening
session. To play a station saved from an earlier session, find it again with
POST /station filtered on its uuid, and use the id that returns.
Playing a station
To play a station, a client needs to repeatedly make calls to POST /play
(to retrieve a song for playback), POST /play/{play_id}/start (to report
the song has started playback), and then POST /play/{play_id}/complete (to
report that the song has completed playback). To take advantage of the fact
that retrieving audio is much faster than playing audio, the client can make a
call to POST /play to retrieve the next song as soon as a call has been
made to POST /play/{play_id}/start to report that the current song has
started playback. A client can hold at most one song ahead this way: until
the current play is started, POST /play keeps returning the same song.
A station found with POST /station comes with its first song already
reserved, in the response's play. Play that song first and call
POST /play/{play_id}/start on it, rather than calling POST /play for
the first song. From then on, the loop is the same.
While a song plays, the client also calls POST /play/{play_id}/elapse
every 10 to 30 seconds, and again whenever playback is paused or stopped.
That is what records the right listening time if the client disappears
partway through a song.
These reports are what royalties are calculated from. Send
POST /play/{play_id}/start only once audio is reaching the listener, not
when a play is reserved or its file starts loading. Never report a play as
started or completed if no one heard it. Send every report as it happens;
do not batch, sample, or drop them.
When POST /play answers noMoreMusic (code 9), the station has nothing
more for this client. That is the normal way a station ends, not a
failure. Stop requesting from that station rather than retrying.
Song selection, including any limits on consecutive songs by one artist or from one album, happens on the server. Any play it returns may be played as is. Clients must not choose, reorder, or filter songs themselves.
If a client is unable to start audio playback from a returned play, then it
should call POST /play/{play_id}/invalidate (rather than POST /play/\{play_id\}/start and POST /play/\{play_id\}/complete) to indicate as
such, and then make another call to POST /play to retrieve a new candidate
to play. Cap this invalidate-and-retry loop, for example at 3 in a row, then
stop the station and surface an error. Note that this doesn't apply to
expired audio file URLs. If a
song was unretrievable because its audio file URL expired (it has an Expires
query parameter that is a unix timestamp in the past), then a new call to
POST /play should be made to generate a new URL for that audio file, and
POST /play/{play_id}/invalidate should not be called.
During playback, a user can request to 'skip' the current song and advance
to the next. The POST /play/{play_id}/skip endpoint makes that request to the
server. Clients must not stop playback of the current song to advance to
another unless the skip request returns success. This protocol is in
place to enforce licensing restrictions. Clients that appear to be playing
songs faster than expected (that is, skipping without permission) will
trigger review and possible credential revocation. A granted skip ends the
play, so do not also call POST /play/{play_id}/complete on it. Play the
next song the client is holding, or call POST /play for one.
Test credentials
To test this API, you may use the following strings as your token and secret values:
demoreturns a set of radio stationscountingreturns a single radio station comprised of very short audio files, each of which is a voice speaking a number (helpful for testing song completion logic).crossfaderreturns a single radio station comprised of 10 second audio files, each of which is a single tone, and a 'crossfade_seconds' value of 3, to assist with testing crossfade functionality.badgeoreturns asession.available: falsevalue, to test clients that may be connecting from a location where no music is available.wordsreturns a single radio station, Words, whose skips follow the normal radio limit of 6 per station per 60 minutes. The seventh play'sPOST /play/{play_id}/startresponse hascan_skip: false, and a skip request on it is refused withskipDenied(code 7). Use a new client (aPOST /sessionwithout aclient_id) for each test run, because a client stays at the limit for up to an hour.twowordsreturns a single radio station, TwoWords, that has only two songs a client may hear in a row. After a client has played both, its nextPOST /playon that station answersnoMoreMusic(code 9), which is how a station normally ends. Use a new client for each test run.
UI and other requirements
Client requests for music must come from the IP address of the device that will play the music, so that geo-IP mapping represents the true location of the device. That is, requests must not be proxied to appear to come from IP addresses in different geographic locations.
Clients that play music via this API are subject to display requirements. In addition to any requirements specified by your customer success representative, apps that play music with this API must obey the following:
- A song's title, performing artist, and album name must be displayed for 5 seconds when the new song starts playing.
- 'like' and 'skip' buttons must not be enabled for clients when the
respective
can_likeorcan_skipvalue for the active play is false. - Clients must not skip the active play absent a
success: trueresponse to aPOST /play/{play_id}/skipcall. - A 'Powered by Feed.fm' or the Feed.fm logo must be visible in the UI.
- The following disclaimer must be no more than two user clicks from the music
experience:
There is no affiliation, connection, association or endorsement of the products, goods or
services displayed on this page by the copyright owners, featured recording artists and
authors of the sound recordings (and the musical works embodied therein) transmitted
through the Feed.fm player.
Authentication
- HTTP: Basic Auth
HTTP Basic authentication with your Feed.fm token as the username and
the matching secret as the password. The token may be a consumer token
or an access token from POST /access_token. Clients that cannot set
Authorization may send the identical value in an X-Authorization
header — see Authentication in the introduction.
Security Scheme Type: | http |
|---|---|
HTTP Authorization Scheme: | basic |