Skip to main content
Version: 3.0.0

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 set Authorization.

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:

  1. client_id in the request body
  2. a cid cookie
  3. client_id in 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​

codemnemonicHTTPmeaning
5badCredentials401Credentials missing or invalid.
6forbidden403The credentials are valid but may not do this.
7skipDenied200The listener has no skips available.
9noMoreMusic200No song can be played right now for this client and station.
12playNotActive200The play is not currently being played.
15invalidParameter400A parameter was present but malformed.
16missingParameter400A required parameter was absent.
17missingObject404No such play, station, placement or token.
18internalError500Something went wrong on our side.
19noMusic / notUS403No music is available for this client.
20playbackStarted403This play was already started or invalidated.
21playbackComplete403This play was already skipped or invalidated.
22throttled429Too many errors from this client; slow down.
23notOnDemandOrReplay403This station does not support on-demand playback.
24formatUnavailable200No 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 — passing force200 in the query string or body suppresses the HTTP error status so that failures arrive as HTTP 200 with the real status in error.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 /play with a throttled (22) error and HTTP 429. noMoreMusic (9) answers from POST /play are 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 calling POST /play on 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 DELETE may POST with an X-HTTP-Method-Override: DELETE header. The header is read before routing, so the request is dispatched to the DELETE operation and the POST one 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/v3 produce 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:

  • demo returns a set of radio stations
  • counting returns 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).
  • crossfader returns 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.
  • badgeo returns a session.available: false value, to test clients that may be connecting from a location where no music is available.
  • words returns a single radio station, Words, whose skips follow the normal radio limit of 6 per station per 60 minutes. The seventh play's POST /play/{play_id}/start response has can_skip: false, and a skip request on it is refused with skipDenied (code 7). Use a new client (a POST /session without a client_id) for each test run, because a client stays at the limit for up to an hour.
  • twowords returns a single radio station, TwoWords, that has only two songs a client may hear in a row. After a client has played both, its next POST /play on that station answers noMoreMusic (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_like or can_skip value for the active play is false.
  • Clients must not skip the active play absent a success: true response to a POST /play/{play_id}/skip call.
  • 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 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