Apps and credentials
Everything that connects your product to Feed.fm runs through a pair of credentials: a token and a secret. This page covers where those come from, how to hand them to an untrusted device safely, and which credentials to develop against before you have your own.
It applies whether you integrate with one of our SDKs or call the API directly.
Your apps
An app is the container for the stations your product plays. When you sign up, we create two:
| App | What it is for |
|---|---|
| Production | Your live product. Playback here is billed and reported to rights holders. |
| Development | Testing and development. Playback incurs no charges and reports no analytics. |
Each app has its own token and secret, which you will find in the Customer Portal. The portal is also where you see the stations each app carries.
Develop against the development app. It exists so you can exercise playback repeatedly without generating charges or polluting your reporting. The development app may also hold different stations and configuration, allowing you to test against station changes before they are made to the production app.
Do not ship your development credentials in your production app. Plays made with them are not reported, so you lose both your own analytics and the play reporting we pass to rights holders. Feed.fm reserves the right to revoke any app found using development credentials in production.
Consumer and access credentials
The pair from the Customer Portal is your consumer credentials. They are long-lived and they are the keys to your app, so they belong on a server you control.
That is a problem for a client application. Whatever you compile into a mobile binary or serve in a web page reaches every one of your users, where it can be extracted and reused elsewhere.
Access credentials solve that. They are additional token and secret pairs that you mint from your consumer credentials, that work anywhere the consumer pair works, and that expire on their own. Your backend holds the consumer credentials and hands the device only a short-lived pair. If one leaks, it stops working by itself, and you can revoke it before then.
An access credential is time-limited, not permission-limited. While it is valid it grants the same access your consumer pair does.
Generating an access credential
There is no SDK method for this. Your backend calls the API directly, using your consumer credentials, and passes the result to the client.
curl -sS -u "$FEEDFM_TOKEN:$FEEDFM_SECRET" \
-X POST https://feed.fm/api/v3/access_token \
-H 'Content-Type: application/json' -d '{"ttl_seconds": 3600}'
{
"success": true,
"access_token": {
"token": "kD8vQ2mXw5tR9cLpZ3nHbY7fJ4sA6gE1",
"secret": "uT0iN8rW2yB5xM3qV7dK9pF4hC6zL1oG"
}
}
Hand that token and secret to the client exactly as you would the consumer pair. Every SDK and every API endpoint accepts it.
Three things the response does not tell you:
ttl_secondsdefaults to 86400 (24 hours) when you omit it.- Values above 2592000 (30 days) are silently clamped, not rejected. The
call still returns
success: trueand the token simply expires earlier than you asked for. - There is no expiry in the response. Record the lifetime you requested rather than assuming you got it.
Pick a lifetime that matches a realistic listening session, and mint a fresh pair each time your app starts the player rather than storing one on the device. Expiry is not signalled in advance.
Revoking an access credential
curl -sS -u "$FEEDFM_TOKEN:$FEEDFM_SECRET" \
-X DELETE https://feed.fm/api/v3/access_token/kD8vQ2mXw5tR9cLpZ3nHbY7fJ4sA6gE1
Revoke when a user logs out, when a session ends early, or when you think a credential has been compromised. The request must use the consumer credentials that created the token.
For how credentials travel on an HTTP request, see REST API: Credentials.
Handing credentials to your app
The pattern is a small endpoint on your own backend that authenticates your user however you normally would, mints a pair scoped to a listening session, and returns only that pair.
// Node/Express. Runs on YOUR server, where the consumer
// token and secret never leave.
app.post('/music-credentials', requireLoggedInUser, async (req, res) => {
const auth = Buffer.from(
`${process.env.FEEDFM_TOKEN}:${process.env.FEEDFM_SECRET}`
).toString('base64');
const response = await fetch('https://feed.fm/api/v3/access_token', {
method: 'POST',
headers: {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/json'
},
// good for four hours
body: JSON.stringify({ ttl_seconds: 14400 })
});
const body = await response.json();
if (!body.success) {
return res.status(500).json({ error: body.error.message });
}
res.json({
token: body.access_token.token,
secret: body.access_token.secret
});
});
The client then initializes with the credentials it was handed, instead of with hardcoded values:
// Javascript SDK
var player = new Feed.Player(credentials.token, credentials.secret);
// iOS SDK
FMAudioPlayer.setClientToken(credentials.token, secret: credentials.secret)
// Android SDK
FeedAudioPlayer player =
new FeedAudioPlayer.Builder(getApplicationContext(),
credentials.token,
credentials.secret)
.build();
The client id that identifies a listener is independent of all this. Swapping credentials does not reset a user's playback history; see Client ID Swapping for how that value is managed.
Detecting a rejected credential
Both native SDKs validate credentials when they create a session at player startup, and that is where an expired pair is reported as a distinct, identifiable error. Watch for it there, and respond by fetching a new pair from your backend and initializing the player again.
iOS
Assign the FMAudioPlayerDelegate before calling
setClientToken(_:secret:), since the session request begins immediately. A
rejected credential arrives as a FeedFMError with code
FeedFMErrorCodeSessionCreationFailed (1203), and the specific cause is carried
in its underlying error, an NSError in the FMAPIErrorDomain with code
FMErrorCodeInvalidCredentials (5):
FMAudioPlayer.shared().delegate = self
FMAudioPlayer.setClientToken(credentials.token, secret: credentials.secret)
// ...
func audioPlayerDidReceiveError(_ error: Error) {
let nsError = error as NSError
guard nsError.code == FeedFMErrorCode.sessionCreationFailed.rawValue,
let underlying = nsError.userInfo[NSUnderlyingErrorKey] as? NSError,
underlying.domain == FMAPIErrorDomain,
underlying.code == FMErrorCode.invalidCredentials.rawValue // 5
else {
return
}
// The credential was rejected. Fetch a new pair and start over.
fetchCredentials { credentials in
FMAudioPlayer.setClientToken(credentials.token, secret: credentials.secret)
}
}
whenAvailable(_:notAvailable:) and a playbackState of
FMAudioPlayerPlaybackStateUnavailable also tell you the player did not come
up, but they carry no error, so they cannot distinguish an expired credential
from a geographic restriction or a network failure. Use the delegate for that.
Android
A rejected credential is delivered to
AvailabilityListener.onPlayerUnavailable() as a
FeedFMError whose apiError is ApiErrorEnum.INVALID_CREDENTIALS (code 5):
FeedAudioPlayer.Builder(context, credentials.token, credentials.secret)
.setAvailabilityListener(object : AvailabilityListener {
override fun onPlayerAvailable(player: FeedAudioPlayer) {
// music is ready
}
override fun onPlayerUnavailable(e: Exception) {
if ((e as? FeedFMError)?.apiError == ApiErrorEnum.INVALID_CREDENTIALS) {
// The credential was rejected. Fetch a new pair and rebuild
// the player. Token and secret are only settable on the Builder.
fetchCredentials { credentials -> buildPlayer(credentials) }
}
}
})
.build()
Because the token and secret are constructor arguments to Builder, recovering
means calling destroyInstance() on the existing player and building a new one.
If a credential expires mid-session
Every API call carries the credentials, so a pair that lapses while the app is
running will cause subsequent calls to fail. Those failures surface as ordinary
transient request errors, retried or reported as generic playback errors, rather
than as a distinct "credentials expired" signal. Do not build your recovery
around catching them. Size ttl_seconds comfortably longer than a realistic
listening session, and mint a fresh pair on each player startup.
Test credentials
Use these to test out an implementation before your own credentials arrive, or to reproduce conditions that are otherwise hard to stage. Pass the same string as both the token and the secret.
| Credential | What you get |
|---|---|
demo | Two ordinary stations, Station One and Station Two. The general-purpose starting point. |
counting | One station, Numbers, playing short clips of a voice counting. Songs end quickly, which makes playback reporting and song transitions fast to test. |
crossfader | One station, Crossfader, configured with a 3 second crossfade, for checking how your player handles the overlap between songs. |
badgeo | A client with no music available, for testing how your app behaves where it cannot stream. |
badgeo is worth understanding before you use it. It is a valid credential, so
the session request succeeds: you get HTTP 200 and success: true, with
session.available set to false and a message explaining there is no music.
It does not produce an authentication error. That makes it the way to test the
path your app takes when a listener cannot be served, which is a real condition
in production whenever someone opens your app outside a licensed territory.
Plays made with test credentials are not billed and are not reported.