API Reference
SongPort API
Turn a single music link into direct links for every major streaming platform — powered by the same ISRC matching that runs songport.link. One request in, all the platform links out.
Overview
The SongPort API resolves a music link (from Spotify, Apple Music, YouTube Music, Deezer, Tidal or SoundCloud) into the matching links on every other platform, plus the track's title, artist and artwork. It's the programmatic version of what the website does when you paste a link.
It's a single JSON endpoint. You send a link, you get back a structured object with a link per platform. Repeated links are served from cache instantly.
Base URL
All requests go to:
https://api.songport.link/v1Every endpoint below is relative to that base and uses HTTPS.
Authentication
Requests are authenticated with an API key, sent as a bearer token in the Authorization header:
Authorization: Bearer sp_live_xxxxxxxxxxxxxxxxxxxxYour key is secret and meant for server-to-server use only. Keep it in your backend — never ship it in browser or mobile-app code, where it could be read and reused. There are no CORS headers on the API for exactly this reason. If a key is ever exposed, it can be revoked instantly and a new one issued.
Don't have a key yet? Reach out (see Support) and we'll set you up.
Converting a link
POST /v1/convert — resolve one music link.
Send a JSON body with these fields:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | yes | A supported music URL (or a spotify: URI). |
platforms | string[] | no | Only return these platforms (e.g. ["spotify", "appleMusic"]). Unknown keys are ignored. |
shorten | boolean | no | When true, return a shareable songport.link short URL (created if this track isn't stored yet). |
Platform keys: spotify, appleMusic, youtubeMusic, deezer, tidal, soundcloud.
The response
A successful call returns 200 with a JSON object:
{
"isrc": "GBARL9300135",
"title": "Never Gonna Give You Up",
"artist": "Rick Astley",
"thumbnail": "https://i.scdn.co/image/ab67616d...",
"platforms": {
"spotify": { "url": "https://open.spotify.com/track/4cOd...", "isSearchFallback": false },
"appleMusic": { "url": "https://music.apple.com/...", "isSearchFallback": true },
"deezer": { "url": "https://www.deezer.com/track/781592622", "isSearchFallback": false }
},
"cached": true,
"songportUrl": "https://songport.link/t/rick-astley-never-gonna-give-you-up-w45sq8xg"
}What each field means:
| Field | Type | Description |
|---|---|---|
isrc | string | null | The recording's ISRC when known. |
title | string | Track title. |
artist | string | Artist name. |
thumbnail | string | null | Cover-art image URL. |
platforms | object | A map of platform key → { url, isSearchFallback }. See below. |
cached | boolean | true when the result came straight from cache (no upstream lookup). |
songportUrl | string? | A shareable short link. Always present on cache hits; on fresh conversions only when you pass shorten: true. |
Each entry in platforms has a url and a boolean isSearchFallback:
isSearchFallback: false— a direct deep link to the exact track on that platform.isSearchFallback: true— no exact match was available, so the URL is a best-effort search on that platform instead. Matching is best-effort per platform; not every track exists everywhere.
Examples
A minimal request with cURL:
curl -X POST https://api.songport.link/v1/convert \
-H "Authorization: Bearer sp_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT"}'The same call from a Node.js backend:
const res = await fetch("https://api.songport.link/v1/convert", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SONGPORT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT",
platforms: ["spotify", "appleMusic"], // optional
}),
})
const data = await res.json()
console.log(data.platforms.appleMusic.url)Errors
Errors use standard HTTP status codes. The body is JSON with an error message (or plain text for auth/rate-limit responses).
| Status | Meaning |
|---|---|
400 | Bad input — missing/invalid url, or malformed platforms. |
401 | Missing, invalid, or inactive API key. |
429 | Rate limited — monthly quota reached, per-minute burst exceeded, or the shared daily capacity for new (uncached) conversions is momentarily full. Back off and retry. |
502 | The link could not be resolved to any platform. |
Rate limits & quota
Each key has a monthly quota (the number of calls per calendar month, defined by your plan) and a short per-minute burst limit.
Every call counts toward your monthly quota — including cache hits. Cache hits are fast and don't hit the upstream services, but they still count as one request. When you exceed a limit you get a 429; wait and retry, or ask for a higher plan.
Versioning
The API is versioned in the path (/v1). v1 is a stable contract: we only add fields to responses, never remove or rename existing ones. Any breaking change ships under a new major version (/v2), and v1 stays supported for a clearly-announced deprecation window after that.
Support
Questions, a new key, or a higher plan? Get in touch at hallo@straatletters.be.