The tuneefy API
API Endpoint
https://data.tuneefy.com/v2Welcome to the tuneefy API. This API provides access to the tuneefy.com service and exposes endpoints to search, aggregate and share music links.
Version
The current version is v2
, which this documentation is for. The legacy API was previously available at https://api.tuneefy.com but has been deprecated. Next versions of the API will reside in the same host, and will be prefixed with, say v3
, v4
, … for instance.
Notational Conventions
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC2119.
Representation of Date and Time
All exchange of date and time-related data, if applicable, MUST be done according to ISO 8601 standard and stored in UTC.
When returning date and time-related data, the YYYY-MM-DDThh:mm:ss+P
format MUST be used.
Rate limiting
You SHOULD NOT hit the API more than 10 times per second. For the agreggate method, the limit is ~10 requests per minute. If you make too many requests, your IP might be blocked without warning. Contact us if this occurs, to find a solution.
This is currently a limitation of the server on which the tuneefy API resides. Remember that this project is provided as-is, and that the costs (server, bandwidth, maintenance) are only covered by donations (that, as of 2018 and from the start, account for a mere €12).
Authentication
The API endpoints require an OAuth access token. The token is necessary to authenticate all requests to the API.
OAuth
The tuneefy API currently supports the OAuth 2 draft specification. All OAuth2 requests MUST use the SSL endpoint available at https://data.tuneefy.com/v2/.
OAuth 2.0 is a simple and secure authentication mechanism. It allows applications to acquire an access token for tuneefy via a POST request to a token endpoint. Authentication with OAuth can be accomplished in the following steps:
-
Register for an API key by sending a mail to api@tuneefy.com
-
Exchange your customer id and secret for an access token
-
Make requests by passing the token in the Authorization header
-
When your token expires, you can get a new one
Getting an authorization token
Use the /auth/token
endpoint to get a valid token. You will need your consumer key and secret.
The only accepted grant type is
client_credentials
. You don’t need to pass scopes to the request. See further down for more info.
Passing the authorization token
You can either use the Authorization header, as such :
Authorization: bearer 5262d64b892e8d4341000001
Or you can pass the access_token
parameter with all your requests.
GET platforms?access_token=5262d64b892e8d4341000001
The header method is RECOMMENDED.
In short
-
This API uses OAuth2 for authentication
-
A token MUST be retrieved from the authentication endpoint before issuing any request
-
Token MUST be provided in a
Authorization
header, or via theaccess_token
parameter -
Token MUST be provided for each request
Pagination
The API can limit the number of results returned (for search
and aggregate
methods) via the limit
parameter.
GET search/track/qobuz?q=chopin&limit=2
This is not strictly speaking pagination since there is no way to pass an offset to the API to retrieve the subsequent results, are they are fetched from different platforms that do not all support pagination.
The default limit is 10
results.
Market search
The API can limit the search to specific geographic markets (for search
and aggregate
methods) via the countryCode
parameter. The code must be an ISO 3166-1 alpha-2 country code.
GET search/track/qobuz?q=chopin&countryCode=IT
Note that this code is only used for Spotify, Tidal, Napster, iTunes, and Youtube.
Deezer, Qobuz, LastFM, Mixcloud, Soundcloud and Amazon Music do not have this concept.
Bear in mind these specificities :
- Youtube: the country code relates to the rights to view a video in a specific country;
The default countryCode is FR
(if you don’t specify it in your query); As a majority of platforms need a market / country code identifier, you cannot use null
or a non-valid value here.
Response format
This API uses the Accept
header to identify the data type it should return.
Accept: application/json
This header SHOULD be present in every request. If not, the API will use the default response format, json.
Alternatively, all API methods support an optional return format parameter format=json
. It takes precedence over the header.
Return type | Accept header | format parameter |
---|---|---|
JSON | Accept: application/json |
format=json |
XML | Accept: application/xml |
format=xml |
XML (alt) | Accept: text/xml |
format=xml |
Info
The API does not support JSONP nor HTML anymore.
Status Codes and Errors
Status codes
This API uses HTTP status codes to communicate with the API consumer.
-
200 OK
- Response to a successful GET or POST. -
400 Bad Request
- Malformed request; form validation errors. -
401 Unauthorized
- When no or invalid authentication details are provided. -
404 Not Found
- When a non-existent resource is requested. -
405 Method Not Allowed
- Method not allowed. -
406 Not Acceptable
- Could not satisfy the request Accept header.
Error codes and responses
This API returns both machine-readable error codes and human-readable error messages in response body when an error is encountered. An error can be encountered even if the response HTTP status code is 200.
-
GENERAL_ERROR
- An error was encountered -
BAD_PLATFORM_TYPE
- This type of platform does not exist -
BAD_PLATFORM
- This platform does not exist -
MISSING_PERMALINK
- Missing or empty parameter : q (permalink) -
PERMALINK_UNKNOWN
- This permalink does not belong to any known platform -
FETCH_PROBLEM
- There was a problem while fetching data from the platform -
FETCH_PROBLEMS
- There was a problem while fetching data from the platforms -
NO_MATCH
- No match was found for this search -
NO_MATCH_PERMALINK
- No match was found for this permalink -
MISSING_QUERY
- Missing or empty parameter : q (query) -
BAD_MUSICAL_TYPE
- This musical type does not exist -
NO_INTENT
- Missing or empty parameter : intent -
NOT_CAPABLE_TRACKS
- This platform is not capable of searching tracks -
NOT_CAPABLE_ALBUMS
- This platform is not capable of searching albums -
NOT_AUTHORIZED
- Not authorized, check the token
Intents
When searching on the tuneefy API, results are returned with an intent code, in the share object :
{
...
share: {
intent: '5911b14860e96',
expires: '2016-05-05T12:30:00'
}
}
This means that each result can be shared easily until the intent expires.
On our side, this allows for a practical way to create tuneefy link without cluttering the database and creating many links for each search result. As well, you don’t necessarily need a tuneefy link when using the tuneefy API, hence the design choice.
To get the tuneefy link from the intent, just call the /share
endpoint with this intent. It will consume the intent and return the tuneefy link corresponding to the search result.
Once an intent is consumed, it is no longer available and calling the
/share
with a consumed or expired intent will result in a 400.
Authentication ¶
OAuth Access token ¶
A valid token MUST be provided for each request that requires authentication.
Headers
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Body
client_id=Your application customer key
&client_secret=Your application customer secret
&grant_type=client_credentials
Headers
Content-Type: application/json
Body
{
"access_token": "5262d64b892e8d4341000001",
"scope": "all",
"expires_in": 3600,
"token_type": "Bearer"
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"access_token": {
"type": "string",
"description": "valid Token"
},
"scope": {
"type": "string",
"description": "scopes of current token"
},
"expires_in": {
"type": "number"
},
"token_type": {
"type": "string"
}
}
}
Headers
Content-Type: application/json
Body
{
"error": "invalid_client",
"error_description": "The client credentials are invalid"
}
Headers
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Body
client_id=Your application customer key
&client_secret=Your application customer secret
Headers
Content-Type: application/json
Body
{
"error": "invalid_request",
"error_description": "The grant type was not specified in the request"
}
Get an access tokenPOST/auth/token
Allows to retrieve a valid OAuth token for your application, using the application consumer key and secret.
This endpoint is not protected
Platforms ¶
Platforms list ¶
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"platforms": [
{
"name": "Deezer",
"type": "streaming",
"homepage": "https://www.deezer.com/",
"tag": "deezer",
"mainAccentColor": "181818",
"enabled": {
"api": true,
"website": true
},
"capabilities": {
"track_search": true,
"album_search": true,
"lookup": true
}
}
]
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"platforms": {
"type": "array"
}
}
}
Headers
Content-Type: application/json
Body
{
"errors": [
{
"BAD_PLATFORM": "This platform type does not exist"
}
]
}
Headers
Accept: "application/json"
Authorization: "Bearer invalid_token"
Headers
Content-Type: application/json
Body
{
"errors": [
{
"NOT_AUTHORIZED": "Not authorized"
}
]
}
Retrieve all PlatformsGET/platforms{?type}
Retrieves the list of available Platforms
List of available types
Type | Underlying interface |
---|---|
streaming |
Platform\WebStreamingPlatformInterface |
store |
Platform\WebStoreInterface |
scrobbling |
Platform\ScrobblingPlatformInterface |
- type
string
(optional) Example: streaming
Platform detail ¶
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"name": "Deezer",
"type": "streaming",
"homepage": "https://www.deezer.com/",
"tag": "deezer",
"mainAccentColor": "181818",
"enabled": {
"api": true,
"website": true
},
"capabilities": {
"track_search": true,
"album_search": true,
"lookup": true
}
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"homepage": {
"type": "string"
},
"tag": {
"type": "string"
},
"mainAccentColor": {
"type": "string"
},
"enabled": {
"type": "object",
"properties": {
"api": {
"type": "boolean"
},
"website": {
"type": "boolean"
}
}
},
"capabilities": {
"type": "object",
"properties": {
"track_search": {
"type": "boolean"
},
"album_search": {
"type": "boolean"
},
"lookup": {
"type": "boolean"
}
}
}
}
}
Headers
Content-Type: application/json
Body
{
"errors": [
{
"BAD_PLATFORM": "This platform does not exist"
}
]
}
Retrieve a PlatformGET/platform/{tag}
Retrieves a specific Platform and its details
See
/platforms
to get a list of all available platforms
- tag
string
(required) Example: deezer
Search ¶
Search ¶
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"results": [
{
"musical_entity": {
"type": "track",
"title": "Karma Police",
"album": {
"title": "OK Computer",
"artist": "Radiohead",
"picture": "https://api.deezer.com/album/14879699/image",
"safe_title": "OK Computer",
"extra_info": {},
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": [
"Deluxe Edition"
]
},
"links": {
"deezer": [
"http://www.deezer.com/track/138539981"
]
},
"safe_title": "Karma Police",
"extra_info": {
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
}
},
"metadata": {
"score": 0.97
},
"share": {
"intent": "590c6c8320b1b"
}
}
]
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"results": {
"type": "array"
}
}
}
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"results": [
{
"musical_entity": {
"type": "album",
"title": "Ok Computer",
"artist": "Radiohead",
"picture": "https://api.deezer.com/album/14879699/image",
"links": {
"deezer": [
"https://www.deezer.com/album/14879699"
]
},
"safe_title": "Ok Computer",
"extra_info": {
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
}
},
"metadata": {
"score": 1
},
"share": {
"intent": "590c6c8320b1b",
"expires": "2016-05-05T12:30:00"
}
}
]
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"results": {
"type": "array"
}
}
}
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"errors": [
{
"MISSING_QUERY": "Missing or empty parameter : q (query)"
}
]
}
Search a specific platformGET/search/{type}/{tag}{?q,mode}
Search for tracks or albums on a specific platform
Capabilities
To be able to search on a platform, the platform must have the track_search or album_search capability.
Mode
The mode indicates whether we’re going to eagerly fetch data when it’s missing from the platform response. It is not used as for now but was handy when Spotify didn’t return the artist when fetching an album. In eager
mode, we made a supplementary call to get the artist info.
Don’t forget to url-encode the query (
q
) if necessary
- type
string
(required) Example: track- tag
string
(required) Example: deezer- q
string
(required) Example: ok+computer- mode
string
(optional) Example: lazy
Lookup ¶
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"result": {
"musical_entity": {
"type": "track",
"title": "On The Road Again",
"album": {
"title": "The Best Of Canned Heat",
"artist": "Canned Heat",
"picture": "https://i.scdn.co/image/b83f79c17a44a9aa24a49748d489c52d80c36fb5",
"safe_title": "The Best Of Canned Heat",
"extra_info": {},
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
},
"links": {
"spotify": [
"https://open.spotify.com/track/6IWxHgVbdKyUMydhVGXRaT"
]
},
"safe_title": "On The Road Again",
"extra_info": {
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
}
},
"metadata": {
"query_words": [
"Canned Heat",
"On The Road Again"
],
"platform": "Spotify"
},
"share": {
"intent": "590c6c8320b1b",
"expires": "2016-05-05T12:30:00"
}
}
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"musical_entity": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"title": {
"type": "string"
},
"album": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"artist": {
"type": "string"
},
"picture": {
"type": "string"
},
"safe_title": {
"type": "string"
},
"extra_info": {
"type": "object",
"properties": {}
},
"is_cover": {
"type": "boolean"
},
"is_remix": {
"type": "boolean"
},
"acoustic": {
"type": "boolean"
},
"context": {
"type": "array"
}
}
},
"links": {
"type": "object",
"properties": {
"spotify": {
"type": "array"
}
}
},
"safe_title": {
"type": "string"
},
"extra_info": {
"type": "object",
"properties": {
"is_cover": {
"type": "boolean"
},
"is_remix": {
"type": "boolean"
},
"acoustic": {
"type": "boolean"
},
"context": {
"type": "array"
}
}
}
}
},
"metadata": {
"type": "object",
"properties": {
"query_words": {
"type": "array"
},
"platform": {
"type": "string"
}
}
},
"share": {
"type": "object",
"properties": {
"intent": {
"type": "string"
},
"expires": {
"type": "string"
}
}
}
}
}
}
}
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"errors": [
{
"PERMALINK_UNKNOWN": "This permalink does not belong to any known platform"
}
]
}
Lookup a permalinkGET/lookup{?q,mode}
Lookup a permalink to retrieve the info about the track or album
Capabilities
To be able to lookup on a platform, the platform must have the lookup capability.
Mode
See search
- q
string
(required) Example: https://open.spotify.com/track/6IWxHgVbdKyUMydhVGXRaT- mode
string
(optional) Example: lazy
Aggregate ¶
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"results": [
{
"musical_entity": {
"type": "track",
"title": "Karma Police",
"album": {
"title": "OK Computer",
"artist": "Radiohead",
"picture": "https://api.deezer.com/album/14879699/image",
"safe_title": "OK Computer",
"extra_info": {},
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": [
"Deluxe Edition"
]
},
"links": {
"deezer": [
"http://www.deezer.com/track/138539981"
]
},
"safe_title": "Karma Police",
"extra_info": {
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
}
},
"metadata": {
"score": 0.97,
"merges": 1
},
"share": {
"intent": "590c6c8320b1b"
}
}
]
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"results": {
"type": "array"
}
}
}
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"errors": [
{
"FETCH_PROBLEM": "There was a problem while fetching data from the platform"
}
],
"results": [
{
"musical_entity": {
"type": "track",
"title": "Karma Police",
"album": {
"title": "OK Computer",
"artist": "Radiohead",
"picture": "https://api.deezer.com/album/14879699/image",
"safe_title": "OK Computer",
"extra_info": {},
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": [
"Deluxe Edition"
]
},
"links": {
"deezer": [
"http://www.deezer.com/track/138539981"
]
},
"safe_title": "Karma Police",
"extra_info": {
"is_cover": false,
"is_remix": false,
"acoustic": false,
"context": []
}
},
"metadata": {
"score": 0.97,
"merges": 1
},
"share": {
"intent": "590c6c8320b1b"
}
}
]
}
Schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"errors": {
"type": "array"
},
"results": {
"type": "array"
}
}
}
Search on multiple platforms at onceGET/aggregate/{type}{?q,mode,aggressive,include}
Search for tracks or albums on multiple platforms
Mode
See search
Include
For aggregation, you can choose which platforms to aggregate to improve the response time of the endpoint. The parameter accepts a string which is a concatenation of the platforms tags separated by a comma.
GET aggregate/track?q=test&include=deezer,spotify,qobuz
If an unknown platform tag is passed, it is ignored. If the include
parameter is empty or not given, the API will agreggate the results of all available platforms that are capable of searching this musical entity type.
Merging aggressively
The aggressive
parameter allows to merge tracks without taking the album name into account, or to merge albums without taking the artist name into account. This works for a majority of scenarios since it’s quite rare that an artist released two tracks with exactly the same name for instance, but it can sometimes confuse live or edit versions, for instance.
Acoustic versions, covers or remix should be correctly differentiated even if you merge aggressively.
- type
string
(required) Example: track- q
string
(required) Example: karma+police- mode
string
(optional) Example: lazy- aggressive
boolean
(optional) Example: false- include
string
(optional) Example: deezer,spotify,qobuz
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"uid": "k0bun0",
"link": "http://tuneefy.com/s/track/k0bun0"
}
Headers
Accept: "application/json"
Authorization: "Bearer 5262d64b892e8d4341000001"
Headers
Content-Type: application/json
Body
{
"errors": [
{
"NO_OR_EXPIRED_INTENT": "No intent with the requested uid"
}
]
}
Postman configuration ¶
Below is a Postman configuration if you want to test the API and fiddle with the parameters:
The Collection (V2) : tuneefy_API.postman_collection.json
The Environment : tuneefy.postman_environment.json
NB : In the collection, you must replace the key and secret with the ones provided for you, and the example intent must be replaced with a real intent from one of your calls to work properly.
API client examples ¶
Here are very simple (and not optimized) examples to get you started. You can find these examples on github in the examples folder.
PHP
We use curl to retrieve the token and then perform a basic search.
<?php
$key = 'administrator';
$secret = 'password';
$host = 'https://data.tuneefy.com/v2';
$tokenEndpoint = $host.'/auth/token';
$searchEndpoint = $host.'/search/track/spotify?q=amon+tobin&limit=1';
// 1. Request token
$tk = curl_init($tokenEndpoint);
curl_setopt($tk, CURLOPT_HTTPHEADER, [
'Content-Type: application/x-www-form-urlencoded;'.
'charset=UTF-8'
]);
curl_setopt($tk, CURLOPT_POSTFIELDS,
'grant_type=client_credentials'.
'&client_id='.$key.
'&client_secret='.$secret
);
curl_setopt($tk, CURLOPT_RETURNTRANSFER, true);
$token = json_decode(curl_exec($tk));
curl_close($tk);
// 2. Use token for search on Spotify
if (isset($token->token_type) && $token->token_type === 'Bearer') {
$br = curl_init($searchEndpoint);
curl_setopt($br, CURLOPT_HTTPHEADER, [
'Authorization: Bearer '.$token->access_token,
'Accept: application/json',
]);
curl_setopt($br, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($br);
curl_close($br);
// 3. Tada !
echo "🎉\n";
var_dump($data);
} else {
echo "Wrong key/secret pair";
}
Shell
We use curl and jq too in a very simple fashion.
#!/bin/bash
response=$(curl -X POST --silent -d client_id=administrator -d client_secret=password -d grant_type=client_credentials https://data.tuneefy.com/v2/'auth/token)
token=$(echo $response | jq --raw-output '.access_token')
echo 🎉
curl --header "Authorization: Bearer $token" "https://data.tuneefy.com/v2/'search/track/spotify?q=amon+tobin&limit=1"
Javascript (node)
We use the request and json modules.
var request = require('request');
var key = 'administrator';
var secret = 'password';
var tokenEndpoint = 'https://data.tuneefy.com/v2/'auth/token';
var searchEndpoint = 'https://data.tuneefy.com/v2/'search/track/spotify?q=amon+tobin&limit=1';
// 1. Request token
request.post({
url: tokenEndpoint,
form: {
'grant_type': 'client_credentials',
'client_id': key,
'client_secret': secret
}
}, function(err, httpResponse, body) {
var json = JSON.parse(body);
if (json.token_type && json.token_type === 'Bearer') {
// 2. Use token for search on Spotify
request({
url: searchEndpoint,
'auth': {
'bearer': json.access_token
},
method: 'GET'
}, function(err, httpResponse, body) {
var json = JSON.parse(body);
// 3. Tada !
console.log("🎉");
console.log(JSON.stringify(json, null, 4));
});
}
});
Python
We use the requests module (standard).
# coding=utf8
import requests, json
key = 'administrator'
secret = 'password'
tokenEndpoint = 'https://data.tuneefy.com/v2/'auth/token'
searchEndpoint = 'https://data.tuneefy.com/v2/'search/track/spotify?q=amon+tobin&limit=1'
# 1. Request token
payload = {'grant_type': 'client_credentials', 'client_id': key, 'client_secret': secret}
req = requests.post(tokenEndpoint,data=payload)
token = req.json()
# 2. Use token for search on Spotify
if token['token_type'] and token['token_type'] == 'Bearer':
headers = {'Authorization': 'Bearer '+token['access_token'], 'Accept': 'application/json'}
req = requests.get(searchEndpoint, headers=headers)
# 3. Tada !
print "🎉"
print json.dumps(req.json(), indent=4, separators=(',', ': '))
else:
print "Wrong key/secret pair"
Generated by aglio on 21 Apr 2024