Tunearo Public API

🚀
New endpoints have been deployed!
Webhooks, artist CRUD, track CRUD, DELETE releases, live metadata updates, multiple primary artists, featured artists, expanded contributor roles, territory controls, and YouTube OAC requests are now available.
July 2026

Distribute music programmatically. Create releases, upload artwork and audio, and submit directly to 35+ streaming platforms — all via REST.

Base URL https://dashboard.tunearo.com/api/public/v1

Overview

The Tunearo API lets you build distribution workflows directly into your own platform. Every request is scoped to the authenticated user — you can only read and modify your own releases.

All requests and responses use JSON. CORS is open (*) so the API can be called from browsers and native apps.

Authentication

Every request must include an Authorization header with a bearer token:

Authorization: Bearer tn_live_xxxxxxxxxxxxxxxxxxxx

Getting an API key

  1. Visit tunearo.com/whitelabel and select your desired plan based on track usage
  2. Click Get Started
  3. Our team will send over terms and a payment link within 24 hours
  4. After payment, your API key will be issued within 12 hours
ℹ Keys are hashed in the database and do not expire. If you lose your key, contact support@tunearo.com to have it revoked and reissued.

Release Flow

A typical release goes through these steps in order:

1
Create an artist POST /artists
2
Create a draft release POST /releases
3
Upload cover artwork POST /releases/{id}/artwork-upload-url → PUT to signed URL
4
Add a track POST /releases/{id}/tracks
5
Upload audio POST /releases/{id}/tracks/{trackId}/audio-upload-url → PUT to signed URL
6
Submit for review POST /releases/{id}/submit
7
Poll for status GET /releases/{id}/status — check for approved, rejected, or live
ℹ If a release is rejected, the rejection_reason field explains why. Fix the issues, PATCH the release, and resubmit.

Artists

Artists are reusable profiles linked to releases. Create an artist once and reference them across multiple releases.

GET /artists List your artists â–¼

Returns all artists scoped to your API key.

Response 200

{
  "artists": [
    {
      "id": "1809afb0-a85f-4636-9124-81d1efd9e340",
      "artist_name": "Aria Vox",
      "biography": "Electronic producer based in London.",
      "spotify_id": null,
      "apple_id": null,
      "created_at": "2026-06-25T18:04:41.180609+00:00"
    }
  ]
}
POST /artists Create an artist â–¼

Request body

FieldTypeRequiredDescription
artist_namestringRequiredDisplay name of the artist
biographystringOptionalShort artist bio
spotify_idstringOptionalSpotify artist ID
apple_idstringOptionalApple Music artist ID
profile_picture_urlstringOptionalStorage path to profile image

Example

curl -X POST "https://dashboard.tunearo.com/api/public/v1/artists" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "artist_name": "Aria Vox",
    "biography": "Electronic producer based in London."
  }'

Response 201

{
  "artist": {
    "id": "1809afb0-a85f-4636-9124-81d1efd9e340",
    "artist_name": "Aria Vox",
    "biography": "Electronic producer based in London.",
    "created_at": "2026-06-25T18:04:41.180609+00:00"
  }
}
GET /artists/{id} Get a single artist â–¼

Returns a single artist by ID. Returns 404 if the artist doesn't exist or belongs to another user.

Response 200

{ "artist": { "id": "...", "artist_name": "Aria Vox", ... } }
PATCH /artists/{id} Update an artist â–¼

Update any artist field. All fields are optional — only pass what you want to change.

Request body

FieldTypeDescription
artist_namestringDisplay name
biographystringArtist bio
spotify_idstringSpotify artist ID
apple_idstringApple Music artist ID
instagram_idstringInstagram handle
soundcloud_idstringSoundCloud ID
audiomack_idstringAudiomack ID
youtube_channel_idstringYouTube channel ID
profile_picture_urlstringStorage path to profile image

Example

curl -X PATCH ".../artists/1809afb0-..." \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "spotify_id": "4Z8W4fkeB5G5Gg", "biography": "Updated bio." }'

Response 200

{ "artist": { "id": "...", "artist_name": "Aria Vox", "spotify_id": "4Z8W4fkeB5G5Gg", ... } }
DELETE /artists/{id} Delete an artist â–¼

Permanently deletes an artist. Returns 404 if not found or not owned by the caller.

Response 200

{ "success": true }

YouTube Official Artist Channel (OAC) Requests 🆕

Submit and track requests for YouTube Official Artist Channel linking. An OAC merges an artist's topic channel with their official channel on YouTube, unlocking the music shelf and verified badge.

âš  An artist must have at least 2 live releases before an OAC request can be submitted. Only one active request is permitted per artist at a time.
POST /artists/{artistId}/oac-request Submit an OAC request â–¼

Submits a YouTube Official Artist Channel request for the given artist. The artist must have at least 2 live releases and no existing pending or approved OAC request.

Request body

FieldTypeRequiredDescription
youtube_topic_channel_urlstringRequiredThe artist's YouTube auto-generated topic channel URL
youtube_main_channel_urlstringRequiredThe artist's official YouTube channel URL
ℹ The artist ID can be retrieved from GET /artists or from the artists array returned on any release response.

Example

curl -X POST ".../artists/68d69cf0-.../oac-request" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "youtube_topic_channel_url": "https://www.youtube.com/channel/UC_topic_id",
    "youtube_main_channel_url": "https://www.youtube.com/channel/UC_main_id"
  }'

Response 201

{
  "oac_request": {
    "id": "ffbe5e36-fbd5-4e03-a6d2-71860149f84d",
    "artist_id": "68d69cf0-f0d9-4b40-9787-9681eeae517a",
    "youtube_topic_channel_url": "https://www.youtube.com/channel/UC_topic_id",
    "youtube_main_channel_url": "https://www.youtube.com/channel/UC_main_id",
    "status": "pending",
    "created_at": "2026-07-30T12:17:55.04196+00:00"
  }
}

Errors

StatusErrorMeaning
404Artist not foundArtist doesn't exist or belongs to another user
409An OAC request already exists for this artistA pending or approved request already exists
422Artist must have at least 2 live releases before submitting an OAC requestNot enough live releases
GET /artists/{artistId}/oac-request Get OAC request status â–¼

Returns the current OAC request for the given artist. Poll this endpoint to check if the request has been approved or rejected by the Tunearo team.

Example

curl ".../artists/68d69cf0-.../oac-request" \
  -H "Authorization: Bearer tn_live_xxx"

Response 200

{
  "oac_request": {
    "id": "ffbe5e36-fbd5-4e03-a6d2-71860149f84d",
    "artist_id": "68d69cf0-f0d9-4b40-9787-9681eeae517a",
    "youtube_topic_channel_url": "https://www.youtube.com/channel/UC_topic_id",
    "youtube_main_channel_url": "https://www.youtube.com/channel/UC_main_id",
    "status": "pending",
    "created_at": "2026-07-30T12:17:55.04196+00:00",
    "updated_at": "2026-07-30T12:17:55.04196+00:00"
  }
}

Returns 404 if no OAC request exists for this artist.

Releases

GET /releases List releases â–¼

Query parameters

ParamDescription
statusFilter by status: draft pending approved rejected live takedown_requested taken_down
limitNumber of results (default 50)
offsetPagination offset

Response 200

{
  "data": [ ... ],
  "count": 11,
  "limit": 50,
  "offset": 0
}
POST /releases Create a draft release â–¼

Request body

FieldTypeRequiredDescription
titlestringRequiredRelease title
primary_artist_namestringRequiredArtist display name (used as fallback string)
artist_idsarrayOptionalArray of Tunearo artist UUIDs. First entry becomes main_primary_artist_id. Supports multiple primary artists.
release_typestringRequiredSingle, Album, or EP (case-sensitive)
primary_genrestringRequiredPrimary genre
release_datestringRequiredISO date: 2026-09-01
label_namestringRequiredLabel or Self-released
languagestringOptionalMetadata language e.g. English
explicit_contentbooleanOptionalDefault false
secondary_genrestringOptionalSecondary genre
copyright_yearintegerOptionalCopyright year e.g. 2026
copyright_holderstringOptionalCopyright holder name
licensed_territories_includestringOptionalPipe-separated ISO 3166-1 alpha-2 country codes e.g. GB|US|DE, or WORLD (default)
licensed_territories_excludestringOptionalPipe-separated country codes to exclude e.g. CN|RU
⚠ release_type is case-sensitive. Use Single, Album, or EP — not lowercase.

Multiple primary artists

Pass an array of Tunearo artist UUIDs in artist_ids. The first entry is set as the main primary artist. The full artist objects are returned in the response under artists and main_artist.

Example — single artist

curl -X POST "https://dashboard.tunearo.com/api/public/v1/releases" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Midnight Drive",
    "primary_artist_name": "Aria Vox",
    "artist_ids": ["1809afb0-a85f-4636-9124-81d1efd9e340"],
    "release_type": "Single",
    "primary_genre": "Electronic",
    "language": "English",
    "release_date": "2026-09-01",
    "label_name": "Self-released",
    "explicit_content": false
  }'

Example — multiple primary artists

curl -X POST "https://dashboard.tunearo.com/api/public/v1/releases" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Midnight Drive",
    "primary_artist_name": "Aria Vox & DJ Nova",
    "artist_ids": [
      "1809afb0-a85f-4636-9124-81d1efd9e340",
      "d103206d-977d-402e-bb4d-4238f644be75"
    ],
    "release_type": "Single",
    "primary_genre": "Electronic",
    "language": "English",
    "release_date": "2026-09-01",
    "label_name": "Self-released",
    "licensed_territories_include": "GB|US|DE|FR",
    "licensed_territories_exclude": "CN",
    "explicit_content": false
  }'

Response 201

{
  "release": {
    "id": "ee5dde1d-7106-4293-8e91-65b020820d2f",
    "title": "Midnight Drive",
    "artist_name": "Aria Vox & DJ Nova",
    "status": "draft",
    "release_type": "Single",
    "release_date": "2026-09-01",
    "main_primary_artist_id": "1809afb0-...",
    "key_artist_ids": ["1809afb0-...", "d103206d-..."],
    "licensed_territories_include": "GB|US|DE|FR",
    "licensed_territories_exclude": "CN",
    "artists": [
      { "id": "1809afb0-...", "artist_name": "Aria Vox", ... },
      { "id": "d103206d-...", "artist_name": "DJ Nova", ... }
    ],
    "main_artist": { "id": "1809afb0-...", "artist_name": "Aria Vox", ... },
    "created_at": "2026-07-23T13:59:54.770675+00:00"
  }
}
GET /releases/{id} Get a release â–¼

Returns the full release object including all metadata fields.

Response 200

{ "release": { ... } }
PATCH /releases/{id} Edit a release â–¼

Update any release field. Only allowed when status is draft or rejected. Attempting to edit a pending or live release returns 409.

Example

curl -X PATCH "https://dashboard.tunearo.com/api/public/v1/releases/ee5dde1d-..." \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Midnight Drive (Remix)", "primary_genre": "Dance" }'
GET /releases/{id}/status Check release status â–¼

Lightweight endpoint for polling. Returns status, rejection reason, and UPC once the release is live.

Response 200

{
  "id": "ee5dde1d-7106-4293-8e91-65b020820d2f",
  "status": "rejected",
  "rejection_reason": "Artwork does not meet requirements.",
  "upc": null,
  "updated_at": "2026-06-25T18:16:37.564131+00:00"
}
POST /releases/{id}/submit Submit for review â–¼

Moves a release from draft or rejected → pending. The Tunearo team reviews and either approves or rejects with a reason.

Response 200

{
  "release": {
    "id": "ee5dde1d-7106-4293-8e91-65b020820d2f",
    "status": "pending",
    "updated_at": "2026-06-25T18:13:22.316584+00:00"
  }
}
POST /releases/{id}/takedown Request takedown â–¼

Requests a takedown of a live release. Pass a reason for the request.

Request body

FieldTypeRequiredDescription
reasonstringRequiredReason for takedown request

Example

curl -X POST "https://dashboard.tunearo.com/api/public/v1/releases/ee5dde1d-.../takedown" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Replacing master recording" }'
PATCH /releases/{id}/metadata Update metadata on a live release 🆕 ▼

Update metadata on a release that is already live. On success, the release status is automatically reset to pending for re-review by the Tunearo team before changes are pushed to DSPs.

Use PATCH /releases/{id} for editing draft or rejected releases. This endpoint is exclusively for post-live metadata updates.

âš  This endpoint returns 409 if the release is not in live status. Also fires a release.submitted webhook event.

Example

curl -X PATCH ".../releases/7b2fea30-.../metadata" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "primary_genre": "Electronic", "label_name": "Tunearo Records" }'

Response 200

{
  "release": {
    "id": "7b2fea30-...",
    "status": "pending",
    "primary_genre": "Electronic",
    "label_name": "Tunearo Records",
    "updated_at": "2026-07-23T08:51:59.67057+00:00",
    ...
  }
}
DELETE /releases/{id} Delete a draft release 🆕 ▼

Permanently deletes a release. Only permitted when status is draft. Returns 409 if the release is in any other status.

Response 200

{ "success": true }

Tracks

Tracks belong to a release and represent individual audio files. Your plan includes a set number of tracks across your entire account — not per release.

âš  When your account track limit is reached, POST /releases/{id}/tracks returns 403 Track limit reached (n). Contact support@tunearo.com to upgrade your plan.
GET /releases/{id}/tracks List tracks on a release â–¼

Returns all tracks attached to the release, ordered by position.

Response 200

{ "tracks": [ ... ] }
POST /releases/{id}/tracks Add a track â–¼

Request body

FieldTypeRequiredDescription
titlestringRequiredTrack title
track_numberintegerRequiredPosition on the release
primary_artist_namestringOptionalTrack artist (defaults to release artist)
featured_artist_idsarrayOptionalArray of Tunearo artist UUIDs for featured artists
featured_artist_namesstringOptionalFallback string e.g. "feat. Artist A, Artist B"
explicit_contentbooleanOptionalDefault false
isrcstringOptionalExisting ISRC if you have one
languagestringOptionalTrack language
lyricsstringOptionalFull lyrics text
composerstringOptionalComposer name
producerstringOptionalProducer name
mixerstringOptionalMix engineer name
songwriterstringOptionalSongwriter name
lyriciststringOptionalLyricist name
mastering_engineerstringOptionalMastering engineer name
publisherstringOptionalPublisher name
remixerstringOptionalRemixer name

Example

curl -X POST ".../releases/{id}/tracks" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Midnight Drive",
    "track_number": 1,
    "primary_artist_name": "Aria Vox",
    "featured_artist_ids": ["d103206d-977d-402e-bb4d-4238f644be75"],
    "songwriter": "John Smith",
    "composer": "Aria Vox",
    "producer": "DJ Nova",
    "publisher": "Nova Publishing Ltd",
    "explicit_content": false
  }'

Response 201

{
  "track": {
    "id": "a9190026-...",
    "release_id": "ee5dde1d-...",
    "title": "Midnight Drive",
    "position": 1,
    "artist": "Aria Vox",
    "featured_artists": "DJ Nova",
    "songwriter": "John Smith",
    "composer": "Aria Vox",
    "producer": "DJ Nova",
    "publisher": "Nova Publishing Ltd",
    "contributors": [
      { "name": "DJ Nova", "role": "Featured Artist" },
      { "name": "John Smith", "role": "Songwriter" },
      { "name": "Aria Vox", "role": "Composer" },
      { "name": "DJ Nova", "role": "Producer" },
      { "name": "Nova Publishing Ltd", "role": "Publisher" }
    ],
    "isrc": null,
    "created_at": "2026-07-23T14:00:23.833755+00:00"
  }
}
GET /releases/{id}/tracks/{trackId} Get a single track 🆕 ▼

Returns a single track by ID. Returns 404 if not found or not owned by the caller.

Response 200

{ "track": { "id": "...", "title": "Midnight Drive", ... } }
PATCH /releases/{id}/tracks/{trackId} Update a track 🆕 ▼

Update any track field. Only permitted when the release is in draft or rejected status — returns 409 otherwise.

Request body

FieldTypeDescription
titlestringTrack title
track_numberintegerPosition on the release
primary_artist_namestringTrack artist name
featured_artist_idsarrayArray of Tunearo artist UUIDs for featured artists
featured_artist_namesstringFallback string for featured artists
songwriterstringSongwriter name
lyriciststringLyricist name
mastering_engineerstringMastering engineer name
publisherstringPublisher name
remixerstringRemixer name
explicit_contentbooleanExplicit flag
isrcstringISRC code
languagestringTrack language
lyricsstringFull lyrics text
composerstringComposer name
producerstringProducer name

Example

curl -X PATCH ".../releases/{id}/tracks/{trackId}" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Midnight Drive (Edit)", "composer": "Aria Vox", "language": "English" }'

Response 200

{ "track": { "id": "...", "title": "Midnight Drive (Edit)", "composer": "Aria Vox", ... } }
DELETE /releases/{id}/tracks/{trackId} Delete a track 🆕 ▼

Permanently removes a track from a release. Only permitted when the release is in draft or rejected status.

Response 200

{ "success": true }

File Uploads

Files are uploaded directly to Tunearo's storage (Supabase). The process is two steps: get a pre-signed URL from the API, then PUT the file directly to that URL.

ℹ Pre-signed URLs expire after 2 hours. Get a fresh URL if your upload is delayed.
POST /releases/{id}/artwork-upload-url Get cover art upload URL â–¼

Request body

FieldTypeRequiredDescription
content_typestringRequiredimage/jpeg or image/png

Response 200

{
  "upload_url": "https://xvmaxcwridovgrrrdldp.supabase.co/storage/v1/object/upload/sign/...",
  "path": "b2841ad4-.../1782411144951-artwork.jpg",
  "bucket": "release-artwork"
}

Then upload the file

# Step 1 — get the URL
UPLOAD=$(curl -s -X POST ".../artwork-upload-url" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"image/jpeg"}')

URL=$(echo $UPLOAD | jq -r .upload_url)
PATH=$(echo $UPLOAD | jq -r .path)

# Step 2 — PUT the file directly to storage
curl -X PUT "$URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @cover.jpg

# Step 3 — save the path back to the release
curl -X PATCH ".../releases/{id}" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d "{\"cover_artwork_url\": \"$PATH\"}"
POST /releases/{id}/tracks/{trackId}/audio-upload-url Get audio upload URL â–¼

Request body

FieldTypeRequiredDescription
content_typestringRequiredaudio/wav or audio/flac
extensionstringRequiredwav or flac

Response 200

{
  "upload_url": "https://xvmaxcwridovgrrrdldp.supabase.co/storage/v1/object/upload/sign/...",
  "path": "b2841ad4-.../ee5dde1d-.../1782411180631-audio.wav",
  "bucket": "track-audio"
}

Then upload the file

# Step 1 — get the URL
UPLOAD=$(curl -s -X POST ".../tracks/{trackId}/audio-upload-url" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"audio/wav","extension":"wav"}')

URL=$(echo $UPLOAD | jq -r .upload_url)

# Step 2 — PUT the file directly to storage
curl -X PUT "$URL" \
  -H "Content-Type: audio/wav" \
  --data-binary @track.wav

Royalties

Query earnings from royalty reports imported by Tunearo. All figures are scoped to your API key's user and returned in GBP. Filters compose freely — combine release_id with period, date ranges, and more.

ℹ Royalty data is available after Tunearo processes your monthly statement from the distributor. Figures reflect what has been reported — not necessarily what has been paid out yet.
GET /royalties Earnings summary â–¼

Returns lifetime and current-month earnings, available balance, and a per-source breakdown. Pass optional filters to narrow the results.

Query parameters

ParamTypeDescription
release_iduuidFilter to a single release (must be owned by caller)
track_iduuidFilter to a single track (must be owned by caller)
periodYYYY-MMFilter to a calendar month e.g. 2026-05
fromYYYY-MM-DDRange start (inclusive)
toYYYY-MM-DDRange end (inclusive)

Example — all earnings

curl "https://dashboard.tunearo.com/api/public/v1/royalties" \
  -H "Authorization: Bearer tn_live_xxx"

Example — single release, single month

curl "https://dashboard.tunearo.com/api/public/v1/royalties?release_id=095c50ec-...&period=2026-05" \
  -H "Authorization: Bearer tn_live_xxx"

Response 200

{
  "currency": "GBP",
  "lifetime_earnings": 10.39,
  "this_month_earnings": 10.39,
  "available_balance": 10.39,
  "transaction_count": 10,
  "by_source": [
    { "source": "Spotify - Stream",        "amount": 5.57 },
    { "source": "iTunes - Apple Music",    "amount": 2.10 },
    { "source": "YouTube - Subscription",  "amount": 1.20 },
    { "source": "TikTok - TikTok",         "amount": 0.70 },
    { "source": "Tidal - streaming",       "amount": 0.42 },
    { "source": "Amazon - DE",             "amount": 0.25 },
    { "source": "Deezer - Deezer",         "amount": 0.15 }
  ],
  "first_reported_at": "2026-05-01T00:00:00Z",
  "last_reported_at": "2026-05-31T00:00:00Z",
  "filters": {
    "release_id": null,
    "track_id": null,
    "period": null,
    "from": null,
    "to": null
  }
}
ℹ available_balance is only returned on the unfiltered summary call — it reflects your total account-wide balance. Filtered calls return null for this field.
GET /releases/{id}/royalties Per-release earnings breakdown â–¼

Returns a detailed breakdown for a single release including per-source, per-territory, and per-track splits.

Query parameters

ParamTypeDescription
periodYYYY-MMFilter to a calendar month
fromYYYY-MM-DDRange start (inclusive)
toYYYY-MM-DDRange end (inclusive)

Example

curl "https://dashboard.tunearo.com/api/public/v1/releases/095c50ec-.../royalties" \
  -H "Authorization: Bearer tn_live_xxx"

Response 200

{
  "currency": "GBP",
  "lifetime_earnings": 10.39,
  "this_month_earnings": 10.39,
  "transaction_count": 10,
  "by_source": [
    { "source": "Spotify - Stream", "amount": 5.57 }
  ],
  "by_territory": [
    { "territory": "GB", "amount": 6.77 },
    { "territory": "US", "amount": 2.52 }
  ],
  "by_track": [
    { "track_id": "a9190026-...", "title": "Rrf", "amount": 10.39 }
  ],
  "first_reported_at": "2026-05-01T00:00:00Z",
  "last_reported_at": "2026-05-31T00:00:00Z"
}

Webhooks 🆕

Webhooks let you receive real-time push notifications when events happen on your account — no polling required. Register a publicly accessible HTTPS endpoint and Tunearo will POST a signed JSON payload to it whenever a subscribed event fires.

⚠ The webhook secret is returned once on creation — store it securely. Use it to verify the X-Tunearo-Signature header on every incoming request.

Supported events

EventFired when
release.submittedA release is submitted for review (including metadata updates on live releases)
release.approvedA release is approved by the Tunearo team
release.rejectedA release is rejected — check data.rejection_reason
release.liveA release goes live on DSPs
release.takedown_completedA takedown is completed across all platforms
royalties.importedNew royalty data has been imported for your account

Payload shape

{
  "event": "release.submitted",
  "timestamp": "2026-07-23T08:49:41.259Z",
  "data": {
    "id": "1cc9611b-...",
    "title": "Webhook Test Release",
    "status": "pending",
    ...
  }
}

Signature verification

Every webhook request includes an X-Tunearo-Signature header — an HMAC-SHA256 hex digest of the raw request body, signed with your endpoint's secret. Always verify this before processing.

// Node.js example
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Retries

If your endpoint returns a non-2xx response, Tunearo retries delivery up to 3 times with exponential backoff: 1 minute, 5 minutes, then 15 minutes. After 3 failed attempts the delivery is marked as failed.

POST /webhooks Register a webhook endpoint â–¼

Request body

FieldTypeRequiredDescription
urlstringRequiredPublicly accessible HTTPS URL
eventsarrayRequiredList of events to subscribe to

Example

curl -X POST ".../webhooks" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourplatform.com/webhooks/tunearo",
    "events": ["release.submitted","release.approved","release.rejected","release.live","royalties.imported"]
  }'

Response 201

{
  "webhook": {
    "id": "06f77d6c-d589-4b07-b6df-88127f06b4bd",
    "url": "https://yourplatform.com/webhooks/tunearo",
    "events": ["release.submitted","release.approved","release.rejected","release.live","royalties.imported"],
    "active": true,
    "secret": "whsec_2cfb45d4fcd01f205ff4130be2f6cb1aa0cd559f...",
    "created_at": "2026-07-23T08:47:18.295975+00:00"
  }
}
⚠ The secret is only returned once on creation. Store it immediately — it cannot be retrieved again.
GET /webhooks List webhook endpoints â–¼

Returns all registered webhook endpoints for your account. Secrets are not included in list responses.

Response 200

{ "webhooks": [ { "id": "...", "url": "...", "events": [...], "active": true } ] }
GET /webhooks/{id} Get a webhook endpoint â–¼

Returns a single webhook endpoint by ID.

Response 200

{ "webhook": { "id": "...", "url": "...", "events": [...], "active": true } }
DELETE /webhooks/{id} Delete a webhook endpoint â–¼

Permanently removes a webhook endpoint. No further deliveries will be attempted.

Response 200

{ "success": true }

ISRC & UPC Codes

ISRCs (International Standard Recording Codes) are assigned per track, and UPCs (Universal Product Codes) are assigned per release. Tunearo manages a pool of codes on your behalf.

Requesting codes

To obtain ISRCs or UPCs, email support@tunearo.com with the number of codes you need. Our content team will provide them within 2 hours during business hours.

ℹ Once you have your codes, apply them directly via the API using the existing PATCH endpoints — no separate assignment endpoint needed.

Applying an ISRC to a track

curl -X PATCH ".../releases/{id}/tracks/{trackId}" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "isrc": "GBxxxxxxxxxxxx" }'

Applying a UPC to a release

curl -X PATCH ".../releases/{id}" \
  -H "Authorization: Bearer tn_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "upc": "5063720xxxxxx" }'

Errors

All errors return JSON with an error field and an appropriate HTTP status code.

StatusErrorMeaning
401Missing or invalid Authorization headerNo key or wrong key format
403API access disabledKey exists but API access not enabled for this user
403Account suspendedAccount has been suspended
404Release not foundID doesn't exist or belongs to another user
409Cannot edit release in status '...'Release is not in an editable state
422validation + issues arrayRequest body failed validation
403Track limit reached (n). Contact support to increase your API quota.Your plan's track limit has been reached — contact support to upgrade

Example error response

{
  "error": "validation",
  "issues": [
    {
      "field": "release_type",
      "message": "Invalid enum value. Expected 'Single' | 'Album' | 'EP'"
    }
  ]
}

Release Statuses

A release moves through these statuses during its lifecycle:

draft pending approved live rejected takedown_requested taken_down
StatusDescriptionEditable?
draftCreated but not yet submitted✓ Yes
pendingSubmitted, awaiting review✗ No
approvedApproved, being delivered to stores✗ No
liveLive on streaming platforms✗ No
rejectedRejected — check rejection_reason, fix, and resubmit✓ Yes
takedown_requestedTakedown in progress✗ No
taken_downRemoved from all platforms✗ No

Supported Stores

When a release goes live, it is delivered to all 34 platforms below. Store-level delivery status is not currently available via the API — the release status field reflects overall distribution status.

#Platform
1iTunes / Apple Music + Shazam
2Spotify
3YouTube Music
4Deezer
5Tidal
6Amazon Music
7AMI Entertainment
8Qobuz
9Pandora
10KKBOX
117Digital + Snapchat
12iMusica
13SoundCloud
14iHeartRadio
15Anghami
16SoundExchange
17JioSaavn
18AWA
19NetEase
20Sirius XM
21YouTube Content ID
22Facebook Audio Library
23Facebook Rights Manager
24Mixcloud
25TikTok
26Boomplay
27Peloton
28Audiomack
29Trebel
30Kuack
31Kuaishou
32Audible Magic
33Tuned Global / Line Music
34Lissen

Rate Limits

There are no enforced rate limits on the API in v1. That said, please follow these guidelines to keep the service healthy for everyone:

OperationRecommended limit
Status polling (GET /releases/{id}/status)Once per minute per release
List endpoints (GET /releases, GET /artists)Once per 30 seconds
Write operations (POST, PATCH)No specific limit — use sensibly
ℹ Formal rate limiting with X-RateLimit-Remaining headers is on the roadmap. We'll communicate limits in advance before enforcing them.

Bandwidth notes

Standard API calls (JSON responses) are tiny and effectively free at any realistic scale. File uploads — audio and artwork — go directly to Tunearo's storage layer and do not pass through the API server, so large files don't affect API performance.

Versioning

The current API version is v1, reflected in the base URL: /api/public/v1.

PolicyDetail
Backward compatibilityWe will not make breaking changes to v1 without prior notice
New fieldsNew optional fields may be added to responses at any time — build tolerantly
DeprecationDeprecated endpoints will be flagged with a header and given a minimum 90-day migration window before removal
New versionsBreaking changes will be introduced as /v2 — v1 will continue to work in parallel

API keys

Roadmap

Features planned for future API versions:

FeatureNotes
Rate limitingFormal limits with X-RateLimit-Remaining response headers
Idempotency keysIdempotency-Key header support on POST endpoints to safely retry requests
Per-store delivery statusGranular delivery status per platform
Multiple API keysIssue and manage multiple keys per user with audit logs
Key scopes/permissionsRead-only keys, submission-only keys etc.

Recently shipped ✅

FeatureShipped
Webhooks — release.submitted, release.approved, release.rejected, release.live, release.takedown_completed, royalties.importedJuly 2026
GET / PATCH / DELETE artistsJuly 2026
GET / PATCH / DELETE tracksJuly 2026
DELETE draft releasesJuly 2026
Live metadata updates via PATCH /releases/{id}/metadataJuly 2026
Multiple primary artists via artist_ids array with full artist objects in responseJuly 2026
Featured artists via featured_artist_ids on tracksJuly 2026
Expanded contributor roles — songwriter, lyricist, mastering engineer, publisher, remixerJuly 2026
Territory controls — licensed_territories_include and licensed_territories_excludeJuly 2026
YouTube OAC requests — POST and GET /artists/{id}/oac-requestJuly 2026
ℹ Have a feature request or integration question? Email support@tunearo.com.