Skip to the content
  1. Docs
  2. Managed Transfers

Transfer results and unmatched items

Managed TransfersGuide · 3 min
The Match Results API gives your backend the summary of a transfer and every item that did not move, with the reason and the best guess. Per transfer or as a daily bulk pull.

The Match Results API gives your backend the items that a transfer could not move: the tracks, albums and artists that found no match, or that the destination refused. Use it to show the user what is missing, to retry with your own catalog search, or to measure your catalog gaps.

CallWhat it returns
GET/public/transfers/{transferUuid}The summary: status, counters, source and destination, timestamps.
GET/public/transfers/{transferUuid}/unmatchedThe unmatched and failed items of one transfer, one page at a time.
GET/public/transfers/unmatchedThe unmatched and failed items of every transfer that finished in a time window. For a daily pull.

When to call it

After match.report_ready. The webhook carries resultsUrl, the address of the unmatched items of that transfer. Or once a day, with the bulk endpoint. Do not poll a running transfer: webhooks tell you when it ends.

Authentication

Server to server, with Basic (Client ID and Client Secret). The report holds your users' libraries.

  • No credential: 401.
  • A Client ID token, a DevToken or a one-time token: 403.
  • Managed Transfers off for your account: 403.
  • A transfer of another account: 404, the same answer as a uuid that does not exist.

A signed-in docs session may also read it, so Try it works here. These calls are not billed.

Fetch the unmatched items

curl "https://api.musicapi.com/public/transfers/6a002d4b-f95b-471f-a27d-609a42e10cdb/unmatched?limit=500" \
  -u "$MUSICAPI_CLIENT_ID:$MUSICAPI_CLIENT_SECRET"

Query parameters

reason
string
Return only the rows with this reason (table below). Both lists.
limit
integer · 100
Rows per page, at most 500. Both lists.
cursor
string
The nextCursor of the previous page. Leave it out for the first page. Both lists.
from
ISO 8601required
Bulk only. A transfer is in the window when it finished at or after from.
to
ISO 8601 · now
Bulk only. A transfer is in the window when it finished before to. The window is at most 31 days.

Pagination

Each list answers { "unmatched": [...], "nextCursor": "..." }. Send nextCursor back as cursor, and stop when it is null. The cursor is opaque: do not build or change it.

The rows of one transfer come in a stable order, so a page never repeats or skips a row. The bulk endpoint orders by transfer, then by item. A bulk page can hold fewer rows than limit and still have a nextCursor: keep going until it is null.

One row

One row
{
  "transferUuid": "6a002d4b-f95b-471f-a27d-609a42e10cdb",
  "title": "Low",
  "artist": "Artist A, Artist B",
  "playlistName": "Road trip",
  "reason": "lowConfidence",
  "bestGuess": "Low (Live) — Artist A",
  "bestGuessUrl": "https://tidal.com/track/123456789",
  "status": "unmatched",
  "itemType": "track",
  "matchingPercentage": 55,
  "source": {
    "id": "4uLU6hMCjMI75M1A2tKUQC",
    "entryId": null,
    "name": "Low",
    "artists": ["Artist A", "Artist B"],
    "album": "The Album",
    "isrc": "USRC17607839",
    "durationMs": 201000
  },
  "group": { "type": "playlist", "name": "Road trip", "sourceId": "37i9dQZF1DXcBWIGoYBM5M" },
  "bestGuessCandidate": {
    "id": "123456789",
    "name": "Low (Live)",
    "artists": ["Artist A"],
    "album": "Live",
    "isrc": null,
    "url": "https://tidal.com/track/123456789"
  }
}
FieldMeaning
title, artist, playlistName, reason, bestGuessThe same values as the results page and its CSV export.
statusunmatched: no good match. failed: the destination refused the item.
itemTypetrack, album or artist.
matchingPercentageThe score of the best guess, 0 to 100. null without a guess.
sourceThe item on the source service. A field is null when the source does not give it.
groupThe collection that held the item: playlist, likedTracks, albums, artists or podcasts. sourceId is the source playlist id, null for the other types.
bestGuessCandidateThe closest item on the destination, which we did not add. null when there is none.
bestGuessUrlThe web address of the best guess. null without a guess, or when the service gives no address.

Reasons

reasonWhat happened
notFoundThe destination has no item that matches.
lowConfidenceThe best match scored below your match threshold. bestGuessCandidate holds it.
regionLockedThe match exists, but not in the user's country.
errorThe item failed on the way to the destination (status is failed).
cannotMatchThe user looked at the candidates and refused all of them.
limitReachedThe user's plan allows no more items.

The summary

curl
curl "https://api.musicapi.com/public/transfers/6a002d4b-f95b-471f-a27d-609a42e10cdb" \
  -u "$MUSICAPI_CLIENT_ID:$MUSICAPI_CLIENT_SECRET"

The answer has the status, the counters (total, processed, matched, unmatched, skipped, failed), matchRatePct, durationSeconds, the source and target sides and the timestamps.

matched counts what we added, and skipped what the destination already had. A second run of a finished transfer reports almost everything as skipped and almost nothing as matched: it changed nothing.