Skip to main content

Soundtrack API

This is the documentation for Soundtrack API. If you're missing something, feel free to do pull requests straight into the repo if you have any suggestions - or drop us a line in our public Slack channel.

Soundtrack is a complete music streaming service for businesses. Soundtrack API lets you build display-, control- and monitoring apps on top of Soundtrack. You can learn more about Soundtrack here and get inspired on how to use the API here.

Requirements

You'll need at least one paying Soundtrack account and an API token to get started (unless you use this way to authorize). Soundtrack API is free for all Soundtrack customers.

Getting a grasp

We've built Soundtrack API using GraphQL. One big upside is that you only get what you ask for (we list more upsides here).

To get a quick grasp of the API, lets take an example: you want to add some info in your app of what's currently playing in your store.

In this query, you ask for the track name and name(s) of the artist(s). What do you get in response? A classic blob of all-data-existing-on-this-endpoint á la REST that you then need to parse? No, the response is... track name and artist(s) name(s).

There's obviously a bunch of more stuff you can fetch and control. Keep scrolling to explore some of it (for the full API reference click here).

json='{"query": "query {
nowPlaying(soundZone: \"U291bmRabwsMWNhedTc1Nm8v\") {
track {
name
artists {
name
}
}
}
}"}'
echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Response

{
"data": {
"nowPlaying": {
"track": {
"name": "Branches",
"artists": [
{
"name": "Fluida"
}
]
}
}
}
}

The basics

It's not a pre-requisite to know the ins and outs of GraphQL, but we do recommend you to at least read our brief GraphQL intro further down this page.

Your request

Endpoint

There's only one endpoint, unless you're using subscriptions (we'll come to that in a bit).

https://api.soundtrackyourbrand.com/v2

Token

For the token you've received from Soundtrack, we use Basic-auth. Make sure to keep your token safe, we'll block any tokens that are compromised.

You can login without asking us for a token, given you have login credentials to Soundtrack. Just follow these instructions.

-H 'Authorization: Basic your_token'

Content-Type

We do support this content-type however it is not recommended to use it since it will soon be deprecated.

-H 'Content-Type: application/graphql'

In production, use the below content-type and adjust your queries

-H 'Content-Type: application/json'

In production, we recommend that you use parameterised queries (application/json). It decreases the risk for injections and you'll be able to re-use more code.

Body

-d '
{
nowPlaying(soundZone: "soundzone_id") {
track {
name
album {
name
image {
url
}
}
}
}
}
'

Soundtrack API supports queries (get stuff), mutations (do stuff) and subscriptions (know when stuff changes). We'll describe all of these three in a bit, but what you need to know now is: regardless what you are doing, you need to send a POST body specifying what you want to get/do.

Our response

{
"data": {
"nowPlaying": {
"track": {
"name": "Sweet Addiction - Live Edit",
"album": {
"name": "Nous Horizon, Vol. 2 (Re-Works & Edits)",
"image": {
"url": "https://l.cdnurl.com/image/31bbda8605500ba6345bd61941fd3cc536ad779"
}
}
}
}
}
}

We will respond with a JSON. Notice that the shape of the JSON in the response follows the shape of the query.

Soundtrack hierarchy

We highly recommend that you play around with your Soundtrack account so you get an overview of the product prior to using the API. All guides and frequently asked questions regarding Soundtrack can be found on our help pages.

As you've seen in the examples, there's something called a "sound zone". In the table below, we've explained the different concepts you need to know about.

ConceptDescription
Accounte.g. "Sven's burgers". Can have multiple locations.
Locatione.g. "Flagship Store, Stockholm". Can have multiple sound zones.
Sound zonee.g. "Bar" or "Lobby". Can only have one device.
DeviceOne of the supported player types. Needed to play music.
UserCan control one or many accounts.

Each sound zone can only output one music stream. So if you want different music in different parts of the same location, you need multiple sound zones (and thus: multiple devices).

If you want the same music everywhere in the same location you’ll only need one sound zone (and one device) and then distribute the music using your audio system.

Each sound zone equals one subscription (which is what costs money).

Asking what you don't know

json='{"query": "query {
me {
...on PublicAPIClient {
accounts(first: 1) {
edges {
node {
businessName
locations(first: 1) {
edges {
node {
name
soundZones(first: 2) {
edges {
node {
id
name
}
}
}
}
}
}
}
}
}
}
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

As you saw in the first example query, with a short query you'll get the info you need - given you know the id (e.g. the id of the sound zone). But how do you get to know what the id is? You ask Soundtrack API.

In this example, we want to know:

  • The businessName of the first account we have access to (e.g. Sven's Burger Corp.)

  • The name of the first location belonging to that account (e.g. Flagship restaurant NYC)

  • The id and name of the first two sound zones belonging to that location (e.g. {lLCwxazBn.., The Bar},{wwNHd3ZTg.., Restaurant area})

As you might notice, there are a two new concepts in this query:

  • Introspection (the me-part) which allows you to explore what you have access to.

  • Connections (the edges, nodes & first: 1) which enables pagination as well as some other things.

For the sake of structure, we'll let you drill down on introspection here and connections here.

Type of actions

Soundtrack API supports queries, subscriptions and mutations.

Entry pointsUse case
QueriesFetch information that usually doesn't change (e.g. sound zone name)
SubscriptionsFetch information that usually changes (e.g. what's currently playing)
MutationsMake changes (e.g. skip to next track)

Queries

Use queries for getting information that you don't expect to change very often. For example: a sound zone id, the name of the account and a list of all your locations.

In the beginning of your query, there's the root query. If you have the id to your account or sound zone, you can input this id and instantly get the information you need. If you don't have the ids, just start off with me to get it, as explained here. All root queries can be found in the reference.

In this example, we ask for the name and the id of a specific sound zone. We also ask whether it is paired to a player or not.

For queries, you actually don't need to start the body with query since GraphQL assumes it's a query if that initial string is missing. However, we recommend including it to make the code self-explanatory.

json='{"query": "query {
soundZone(id:"soundzone_id") {
name
id
isPaired
}
}"}'

echo $json |curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Response

{
"data": {
"soundZone": {
"name": "Staff room",
"isPaired": true,
"id": "U291bmRab25lLCwxanAzcjVsZTlkcy9m8v"
}
}
}

Subscriptions

Use subscriptions for getting information you expect to change often. For example: what is currently playing, current playback state (played/paused) and current sound zone errors.

All subscriptions can be found in the reference.

Endpoint

wss://api.soundtrackyourbrand.com/v2/graphql-transport-ws

For subscriptions there are two things you need to adjust: the endpoint and the way you supply your token.

The endpoint is wss instead of https. That's because we use web sockets for subscriptions (WSS = Web Sockets Secure).

The web socket API adheres to the GraphQL transport WS protocol. The easiest way to connect to it is using their provided packages. Our example app shows how they might be used in a Javascript client.

You won't be using any headers, so the token needs to be passed in when setting up the connection as its payload (e.g. plaintext{"type":"connection_init`,"payload":{"Authorization":"Bearer YOUR_TOKEN],"content-type":"application/json"}}).

Example

subscription nowPlaying {
nowPlayingUpdate(input: {soundZone: "soundzone_id"}) {
nowPlaying {
track {
name
album {
name
image {
url
width
height
}
}
artists {
name
}
}
}
}
}

Initial response

"Your subscription data will appear here after server publication!"

Response once there's data (this response will update)

{
"data": {
"nowPlayingUpdate": {
"nowPlaying": {
"track": {
"name": "Needs",
"artists": [
{
"name": "Loure"
}
],
"album": {
"name": "Smooth Talk EP",
"image": {
"width": 640,
"url": "https://theurltothealbumartwork.com/07dc5282",
"height": 640
}
}
}
}
}
}
}

Since cURL doesn't support web sockets out-of-the box, we don't provide you with a cURL example. Instead: if you want to try subscriptions straight away, check out our playground

In this example we want to get enough information in order to build a neat display screen showing what's playing. For that we need the name of the artist, album & track as well as some album art info.

Once you fire away the request, you'll most likely get an initial response stating that there's not yet any data to display. Once there's anything that is changing, the response will update.

"But I want to show what's playing when a user opens my app - they shouldn't have to wait!"

Good thinking! In those cases you should have an initial query where you ask for what's playing. Then you'll let the subscription take over from there.

"What if the web socket connection is lost in one way or another?"

It's up to you to handle this in your code. Maybe by sending a query (to fetch what's playing) and then re-try the subscription.

"Why can't I just use queries instead of subscriptions?"

With subscriptions you limit the amount of requests as well as avoid getting rate limited. Also, you get the info you need in a more timely manner which makes it easier for you to provide a great user experience!

Mutations

json='{"query":"mutation {
setVolume(input: {soundZone:"soundzone_id", volume: 11}) {
volume
}
}"}'
echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Response

{
"data": {
"setVolume": {
"volume": 11
}
}
}

Use mutations to make changes. For example: change the volume, skip track and play/pause.

All mutations can be found in the reference.

In this example you set the volume to 11 on a specific sound zone. You always need to ask for a response, so in this case we just ask for the volume.

Errors

With GraphQL, a HTTP code stating 200 OK doesn't always mean that you get the data you wanted. If the query executes but bumps into an error along the way, you'll get a 200 with an errors-object appended to the response body.

ErrorDescriptionExample
HTTP 5xx or WebSocket 1xxxServer problemse.g. server not available
HTTP 4xxClient problemse.g. rate limited
HTTP 200 with errors-bodyGraphQL problemse.g. incorrect input

Incorrect input (an account id that doesn't exist)

json='{"query": "query {
account(id:"QWNjb3VudCwsWNheXg3dTc1Nm8v") {
businessName
}
}"}'
echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Response (HTTP 200 OK)

{
"errors": [
{
"path": [
"account"
],
"message": "Forbidden",
"locations": [
{
"line": 2,
"column": 0
}
]
}
],
"data": {
"account": null
}
}

Did you get an error message that you think could be improved? Please reach out on Slack and let us know.

Rate limit

Please note that the rate limit is subject to change since we continuously monitor how the API is used and make relevant updates.

The Soundtrack API enforces rate limiting. You start off with the maximum amount of tokens (3600) and for every call (query, mutation or subscription) you make, tokens will be deducted.

The number of tokens deducted depends on the complexity of the call you are making. But we don’t just take tokens from you - every second you get 50 tokens back.

Let’s say you make a query with a complexity of 1000. Now you’re down to 2600. After 20 seconds you’re back to 3600 tokens (50 tokens per second). Simple, right?

Queries & Mutations

json='{"query: "query {
account(id:"account_id") {
businessName

locations(first: 10) {
edges {
location: node {
soundZones(first: 10) {
edges {
node {
device {
id
}
}
}
}
}
}
}
}
}"}'
curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Queries and mutations calculates the rate limit in the same way. Keep scrolling to understand how we rate limit subscriptions.

In this section's example you’re fetching the device information from the first ten sound zones for each of the first ten locations on a single account. In the same call, you’re also fetching the now playing information form one sound zone. Here’s how we calculate:

FieldCost
accounts1
locations2
soundzones3
device4+1 (since the id field was selected)
total11

A request’s cost is deducted from your tokens. The cost is calculated based on the query, and not the data it would return if it were to execute.

In the example above, even if each location only had one single sound zone, the cost for the sound zones would still be 3 (instead of 2). This means you should try to be conservative with how much data you ask for in deeply nested connections in order to preserve your tokens.

"But I don't want to calculate!"

All calculations are done for every operation you do and will be returned in the headers x-ratelimiting-cost and x-ratelimiting-tokens-available. So for the example above the returned headers would've been:

x-ratelimiting-cost: 11
x-ratelimiting-tokens-available: 3589

Subscriptions

The maximum complexity of a subscription is 100.

Explore

The best way to explore (and, in our opinion, the best documentation too!) is our playground. If you'd like to dig into the schema without authorizing, check out our reference.

Once you’ve added your Authorization header in the bottom left corner you can use it to create and run queries towards the Soundtrack API. There are documentation and auto-completion features.

Please keep in mind that all queries use live data and count towards your rate limit.

Example app

Find the example app in this repo. Feel free to contribute.

To get you started we’ve implemented a simple react-relay app. We’ve decided to create a frontend app since it is easy for you to get up and running locally. However, we strongly encourage you to not build a frontend application where the API client secret can be found in the source code.

Secrets found in public applications will be blocked. What you probably want to do is to build a backend service that implements whatever authentication layer that your app needs.

Terms of use

Read and comply to our API Terms of Use

By using the Soundtrack API, you accept to our API Terms of Use. Please note that it should be clear to the end user that you are the one that has built and is maintaining the application. Using Soundtrack marketing material (except artwork provided via the API) needs Soundtrack Your Brand’s written consent. For example, you are not allowed to use our graphics (e.g. buttons) used in our products to create a similar experience.

Due to licensing restrictions, visitors are currently not allowed to control playback. For example, you are allowed to build an application where your authorized staff changes the playlist but the same functionality can’t be exposed to your visitors, e.g a "jukebox" is not possible to build via our API. Sharing track name, sharing url, etc. is however fine as this is regarded as non-interactive.

Next level

Want to shape up your queries or just read more about a specific topic? We've gathered some good-to-knows here.

GraphQL

The API is built using GraphQL, abiding by the Relay Connection Specification in order to make it as easy as possible to use third party libraries, such as Relay or Apollo.

What is GraphQL?

GraphQL is a query language for APIs. It describes the API as a graph of connected objects which can be queried with a single request. A single query can load data from multiple, deeply nested entities that would normally require multiple chained requests in a REST API.

This makes it easier for you as an application developer to get the data you need, but also improves performance since it minimises the number of requests you need to make.

GraphQL is an API query layer. This means that all queries you run is defined by the API in beforehand. You cannot select data based on any criteria you want, in contrast with other query languages such as SQL.

GraphQL is also an open standard with multiple server and client libraries, which makes it easy to get an application scaffold up and running.

Subscriptions in the Soundtrack API is powered by the GraphQL Transport WS protocol. An implementation of this protocol can be found here. Here are two noteworthy articles on subscriptions in GraphQL: Subscriptions in GraphQL and Relay & Javascript docs.

Why GraphQL?

Here are some of the reasons why we chose GraphQL:

  • Being able to query for the exact data that you need makes it easier to quickly get up and running. And greatly improves the performance.

  • GraphQL is strongly typed and has very good introspection features, which enables very good tooling for both exploring and using the API.

  • There are good client libraries as well as IDE support.

  • The ability to join multiple queries together is a big performance improvement.

Libraries

If you’re building a backend implementation, all you need is an HTTP client and a JSON parser. If you’re building something for the frontend, Relay and Apollo are popular choices. Just make sure you don’t expose your API Credentials by accident (if we find any exposed credentials, we will revoke them).

Additional reading

Introspection

Here's a good read on introspection in GraphQL.

Connections

json='{"query":"query {
node(id: "track_id") {
...on Track {
name
previewUrl
}
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Every one to many relationship is modelled using a concept of connections, edges and nodes. This is according to Relay’s Connection Specification and allows every such relationship to be paginated in a consistent manner.

Node queries

Most of the central entities (account, sound zones, now playing) have specific top-level queries to find them by id.

For many other entities, you can use the more general node query to get a specific entity, should you need it. Use the playground to see what entities implement the node interface (or see the reference).

Pagination: first query

json='{"query":"query {
account(id: "account_id") {
access {
users(first: 5) {
pageInfo {
hasNextPage
endCursor
}
edges {
role
node {
name
id
}
}
}
}
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Pagination: second query

json='{"query": "query {
account(id: "account_id") {
access {
users(first: 5 after: "endCursor") {
pageInfo {
hasNextPage
endCursor
}
edges {
role
node {
name
id
}
}
}
}
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

Pagination

You call the connection describing for example how many results you want and from what page, and then you go through the connection’s edges and get each node. The edge contains metadata about the relationship from the parent to the node, such as a user's role.

The first query would get the first five users who has access to the account. To get the next page, use the endCursor on the pageInfo as a cursor to the next query.

Parameterised queries

The JSON payload is as follows:

{
"query": "query",
"parameters": {"parameter1": "value1"},
"operationName": "only needed if the query has multiple operations"
}

For example:

curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d '
{
"query": "query($id: ID!) { soundZone(id: $id) { name } }",
"variables": { "id": "soundzone_id" }
}
'

A more complex example:

curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d '
{
"query": "query soundZone($id: ID!) { soundZone(id: $id) { name } } query account($id: ID!) { account(id: $id) { businessName } }",
"variables": { "id": "soundzone_id" },
"operationName": "soundZone"
}
'

In production, we recommend that you use parameterised queries. It decreases the risk for injections and you'll be able to re-use more code.

In order to use parameterised queries, the API expects the Content-Type to be application/json.

Fragments & multiple queries

json='{"query":"fragment nowPlayingFields on NowPlaying {
startedAt
track {
name
artists {
name
}
album {
image {
url
width
height
}
}
}
}

query {
nowPlaying1: nowPlaying(soundZone: "soundzone_id_1") {
...nowPlayingFields
}

nowPlaying2: nowPlaying(soundZone: "soundzone_id_2") {
...nowPlayingFields
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Authorization: Basic your_token' \
-H 'Content-Type: application/json' \
-d @-

This example combines multiple queries into a single query. This can be done for all GraphQL you want to run (as long as they are independent from each other) in order to save round trips. It also shows how you can use GraphQL fragments to reduce the amount of boilerplate in your queries.

Authorizing as a user

If you want to, you can skip asking for API client credentials and just get a token using your existing Soundtrack-credentials, using the loginUser-mutation. However, this is not recommended in a production application since if it's abused that user can be rate limited or suspended and therefor not use the product at all onward. For the loginUser-mutation you (obviously...) don't need an authorization-header.

json='{"query": "mutation {
loginUser(input: {email: "your_email", password: "your_password"}) {
token
refreshToken
}
}"}'

echo $json | curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Content-Type: application/json' \
-d @-

The response will contain two tokens:

  • token is your access token which you will use in your other API calls (e.g. when making queries).
  • refreshToken is used for obtaining a new token. We'll get back to this in a bit.

Note that since you're not logging in as an API client, you can't use ...on PublicAPIClient in your me-query (this example). Instead, use ...on User.

If you use this way of authorizing, use Bearer and not Basic

-H 'Authorization: Bearer your_token'

Using token

You use the access token in the same way as already described in these docs, except that you should use Bearer instead of Basic in your authorization header (for subscriptions: as a query parameter)

The token won't be valid forever. That's why you need to use your refreshToken as soon as you get HTTP 401 on your requests.

Using refreshToken

json='{"query": "mutation{
refreshLogin(input:{refreshToken: "your_refresh_token"}){
token
refreshToken
}
}"}'
curl -XPOST https://api.soundtrackyourbrand.com/v2 \
-H 'Content-Type: application/json' \
-d @-

The refreshToken will enable you to fetch a new access token. Once you get the response, replace your old token with the new one.

Always make it a habit to request the refreshToken as well, since it also can change.

If your refreshToken is outdated (your app might have been offline), remember that you always can use the loginUser-mutation to get a new one.