MTShare API
Turn a link from Spotify, Apple Music, YouTube or another music service into one universal link that opens on whichever service the listener uses. The API is free, like the rest of MTShare. It is the same API the MTShare apps use.
Base URL: https://api.mtshare.net/api/v1. Requests and responses are JSON over HTTPS. Send Content-Type: application/json on requests with a body.
On this page
Terms: attribution and limits
- Show "Powered by MTShare". Every integration must display a visible "Powered by MTShare" mention wherever it shows MTShare results, linking to mtshare.net where the format allows it. In a chat bot, a short line under the reply is enough.
- Share the universal link. Send people the
https://mtshare.net/l/<short_code>link rather than copying the platform links out of the response. - Need higher rate limits? The default limits below suit a personal or community bot. If you need more, email [email protected] with what you are building and roughly how many requests a day you expect.
- Be fair to the service. No scraping the catalogue, no reselling access, and keep your token secret. MTShare is a not-for-profit run at cost, so we protect it for everyone.
Get an API token
- Create an account at mtshare.net/register and verify your email address. Creating links needs a verified address.
- Sign in, open your dashboard, go to Profile, and find API tokens.
- Give the token a name (for example "Telegram bot") and select Create token.
- Copy the token straight away. It is shown once. Store it like a password, in your server's secrets, never in a client app or a public repository.
The token is a refresh token for a session of its own. It never contains your password. It appears in the API tokens list on your profile, where you can revoke it at any time. Use one account and one token per integration.
Access tokens and refresh
API calls use a short-lived access token in the Authorization: Bearer header. Get one by exchanging your refresh token:
curl -X POST https://api.mtshare.net/api/v1/auth/token/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token": "YOUR_REFRESH_TOKEN"}'
# 200 OK
{
"access_token": "eyJhbGciOi...",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "NEW_REFRESH_TOKEN",
"user": { "id": "...", "username": "..." }
}- The access token lasts one hour (
expires_inis in seconds). Refresh when it is about to expire, or when a call returns 401. - Every refresh returns a new refresh token. Save it and use it next time; the old one stops working. If a network error means you never saw the response, retrying with the old token within 60 seconds is fine. After that, reusing an old refresh token is treated as theft and ends the session, and you will need a new token from your dashboard.
- It does not expire while you use it. A refresh token expires after 90 days without use, and every refresh restarts that clock. So as long as your integration refreshes regularly (at least once every 90 days), it stays signed in indefinitely.
- Revoking the token on your profile, or choosing "sign out everywhere", ends it immediately.
A small token manager in Python (any HTTP library works the same way):
import time, requests
API = "https://api.mtshare.net/api/v1"
class MTShareAuth:
def __init__(self, load_refresh, save_refresh):
self.load_refresh = load_refresh # read the stored refresh token
self.save_refresh = save_refresh # store the new one (file, secret store...)
self.access = None
self.expires_at = 0
def token(self):
if self.access is None or time.time() > self.expires_at - 60:
r = requests.post(f"{API}/auth/token/refresh",
json={"refresh_token": self.load_refresh()}, timeout=15)
r.raise_for_status()
data = r.json()
self.save_refresh(data["refresh_token"]) # rotate: always keep the newest
self.access = data["access_token"]
self.expires_at = time.time() + data["expires_in"]
return self.access
def headers(self):
return {"Authorization": f"Bearer {self.token()}"}Convert a song
POST /links with the URL of a song on any supported platform. If MTShare already has a link for that song, you get the existing one back.
curl -X POST https://api.mtshare.net/api/v1/links \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://open.spotify.com/track/4uLU6hMCjMI75M1A2tKUQC"}'
# 201 Created
{
"id": "6f1c...",
"short_code": "kJp9wcG",
"link_type": "track",
"metadata": {
"title": "Never Gonna Give You Up",
"artist": "Rick Astley",
"album": "Whenever You Need Somebody",
"album_art_url": "https://..."
},
"platform_links": [
{ "platform": "spotify", "url": "https://open.spotify.com/track/...", "available": true },
{ "platform": "apple_music", "url": "https://music.apple.com/...", "available": true },
{ "platform": "youtube_music", "url": "https://music.youtube.com/...", "available": true }
],
"created_at": "2026-10-27T10:00:00Z"
}Share https://mtshare.net/l/kJp9wcG (the short_code on the end). The request takes a few seconds the first time a song is seen, because MTShare matches it on every platform. Instead of url you can send isrc (for example {"isrc": "GBARL9300135"}) if you already know the recording.
Podcast episodes and shows and audiobook links (for example from Spotify) work through the same call; link_type tells you what came back.
Look up a link
GET /l/{short_code} returns the same data for any existing link. It needs no token.
curl https://api.mtshare.net/api/v1/l/kJp9wcGAlbums and playlists
POST /links/albums and POST /links/playlists take {"url": "..."} for a public album or playlist. They answer straight away with a short_code and "status": "processing", then match the tracks in the background. The universal link works immediately and fills in as tracks are matched.
curl -X POST https://api.mtshare.net/api/v1/links/playlists \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"}'
# Progress of a playlist
curl https://api.mtshare.net/api/v1/links/playlists/SHORT_CODE/status
# {"short_code": "...", "status": "complete", "total_tracks": 50, "matched_tracks": 49}
# Full album or playlist, with every track's links
curl https://api.mtshare.net/api/v1/l/SHORT_CODEFor an album, album_data.album_links also gives the whole album on each platform where MTShare could identify it, once album_data.status is complete. Use it to offer "open the full album" rather than a list of tracks:
"album_data": {
"album_name": "Random Access Memories",
"artist_name": "Daft Punk",
"status": "complete",
"album_links": [
{ "platform": "spotify", "url": "https://open.spotify.com/album/4m2880jivSbbyEGAKfITCa" },
{ "platform": "apple_music", "url": "https://music.apple.com/us/album/random-access-memories/617154241" },
{ "platform": "deezer", "url": "https://www.deezer.com/album/6575789" }
],
"tracks": [ { "title": "Give Life Back to Music", "short_code": "...", "platform_links": { ... } } ]
}A platform appears only when the album was matched exactly (by barcode, or by title, artist and track count). Amazon Music is not included yet.
Two statuses come back without creating a link: not_public when the playlist is private or empty (ask the owner to make it public), and unsupported_platform when that platform has no playlist support yet.
Other endpoints
| Endpoint | Token | What it does |
|---|---|---|
| GET /links/trending?days=7&limit=20 | No | Most viewed and shared links |
| GET /links/platforms | No | The platforms MTShare links to |
| GET /links/my-links | Yes | Links created with your account |
| GET /auth/me | Yes | Your account |
Supported platforms
Links you can send: Spotify, Apple Music, YouTube, YouTube Music, Amazon Music, Deezer, Tidal, Qobuz, Bandcamp, SoundCloud and QQ Music, including their share and short links.
Platforms in the result: Spotify, Apple Music, YouTube, YouTube Music, Amazon Music, Deezer, Tidal and SoundCloud, plus Bandcamp or Qobuz when the link came from there. A platform is listed only when MTShare found the song on it.
Rate limits
Limits are per IP address.
| Requests | Limit |
|---|---|
| Everything, combined | 60 a minute and 1,000 an hour |
| Conversions (POST /links, /links/albums, /links/playlists) | 2 a second, bursts of 5 |
| Sign-in and token refresh (/auth/...) | 5 a second, bursts of 10 |
| Any single endpoint | 10 a second, bursts of 20 |
Over a limit you get 429 Too Many Requests, usually with a Retry-After header in seconds. Wait that long before trying again. Cache results on your side: a song's universal link does not change, so converting the same URL twice is never needed. For higher limits, email [email protected].
Errors
Errors come back as JSON with a detail field that is safe to show to a person.
| 400 | The URL is not from a supported platform, or is not a song, album or playlist link |
| 401 | Missing or expired access token: refresh it and retry once |
| 403 | Not allowed, for example an account whose email is not verified yet |
| 404 | No link with that short code |
| 422 | The request is malformed, or the song could not be found on the source platform |
| 429 | Rate limited: wait for Retry-After |
| 5xx | Our side: retry later with a backoff |
Example: a Telegram bot
A minimal bot with python-telegram-bot that replies to any music link with its universal link, using the token manager above. It keeps the refresh token in a file next to the bot.
import re, requests
from pathlib import Path
from telegram import Update
from telegram.ext import Application, MessageHandler, filters, ContextTypes
TOKEN_FILE = Path("mtshare_refresh_token") # first run: paste your API token here
auth = MTShareAuth(TOKEN_FILE.read_text().strip, TOKEN_FILE.write_text)
URL_RE = re.compile(r"https?://\S+")
async def on_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
match = URL_RE.search(update.message.text or "")
if not match:
return
r = requests.post(f"{API}/links", json={"url": match.group(0)},
headers=auth.headers(), timeout=60)
if r.status_code in (200, 201):
link = r.json()
m = link["metadata"]
await update.message.reply_text(
f"{m['title']} by {m['artist']}\n"
f"https://mtshare.net/l/{link['short_code']}\n"
f"Powered by MTShare"
)
elif r.status_code == 429:
await update.message.reply_text("Busy right now, try again in a minute.")
else:
await update.message.reply_text(r.json().get("detail", "Could not convert that link."))
app = Application.builder().token("TELEGRAM_BOT_TOKEN").build()
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, on_message))
app.run_polling()For a busy bot, keep a cache of URL to short code, and run requests in a thread or with an async HTTP client so one slow conversion does not hold up other chats.
Building something?
Tell us about it, ask for higher limits, or report a problem with the API.
[email protected]