Unison

Unison is a public database of community-synced lyrics, the community-fed source Better Lyrics reads from. Each entry is keyed on a videoId. Reading from it needs no key and no sign-in. Every /lyrics response is wrapped as { success, data }, so check success before you touch data. Reads are rate limited to 120 requests per minute per IP.

Base URL

https://unison.boidu.dev

Quick start

If you have a video id, one call gets you the top-ranked lyrics for it. The synced text is in data.lyrics and its format is in data.format.

curl "https://unison.boidu.dev/lyrics?v=dQw4w9WgXcQ"

Fetch by video

GET /lyrics?v=<videoId> returns the single best version for that video. The fields you need to render are lyrics, format, and syncType; confidence tells you how much the community has vetted it. A miss returns 404.

{
  "success": true,
  "data": {
    "id": 4821,
    "videoId": "dQw4w9WgXcQ",
    "song": "Never Gonna Give You Up",
    "artist": "Rick Astley",
    "album": "Whenever You Need Somebody",
    "duration": 213,
    "format": "ttml",
    "syncType": "richsync",
    "language": "en",
    "score": 42,
    "voteCount": 47,
    "confidence": "high",
    "lyrics": "<tt xmlns=\"http://www.w3.org/ns/ttml\"> ... </tt>"
  }
}

Fetch by song and artist

No video id? Look up by song and artist instead. Both are matched case-insensitively. Add album or duration (seconds, matched within a small tolerance) when a title has more than one recording.

curl "https://unison.boidu.dev/lyrics?song=Never+Gonna+Give+You+Up&artist=Rick+Astley"

Search

GET /lyrics/search?q=<text> is for discovery. It returns ranked candidates with a matchScore but no lyric body, so pick the videoId you want and fetch it with /lyrics?v=. Passing song and artist instead of q runs an exact match and does include the full records. Use limit to cap results.

curl "https://unison.boidu.dev/lyrics/search?q=never+gonna+give"
{
  "success": true,
  "data": [
    {
      "id": 4821,
      "videoId": "dQw4w9WgXcQ",
      "song": "Never Gonna Give You Up",
      "artist": "Rick Astley",
      "format": "ttml",
      "syncType": "richsync",
      "confidence": "high",
      "matchScore": 0.98
    }
  ]
}

Other versions

A video can have several submitted versions. GET /lyrics/variants/:videoId lists them all, best-ranked first (up to 50), so you can offer alternatives if the default is not the one you want. GET /lyrics/:id fetches one specific version by its numeric id.

curl "https://unison.boidu.dev/lyrics/variants/dQw4w9WgXcQ?limit=5"

Translating lyrics

POST /translate turns a block of lyrics into another language and romanizes it in one call, so every client isn't hitting Google's rate-limited endpoint on its own. Send the lines you want, the target language in to, and the source language in from. Leave from out and Unison detects it, then echoes what it picked back as detectedLang.

curl -X POST "https://unison.boidu.dev/translate" \
  -H "content-type: application/json" \
  -d '{"lines":["안녕하세요","반갑습니다"],"to":"en","from":"ko"}'

Each line comes back with a translation, a romanization when the source isn't already in the Latin alphabet, and a needsTranslation flag so you can leave lines that were already in the target language alone. Answers are cached per lyrics-and-language pair, so the same request only reaches Google once; a cache hit sets cached to true. Blank lines and lone ♪ markers keep their place and come back empty.

{
  "lines": [
    { "translation": "Hello", "romanization": "annyeonghaseyo", "needsTranslation": true },
    { "translation": "Nice to meet you", "romanization": "bangapseumnida", "needsTranslation": true }
  ],
  "detectedLang": "ko",
  "provider": "google-lyrics-translate",
  "cached": false
}

When from and to are the same language there's nothing to do, so every line comes back with a null translation and needsTranslation false instead of a copy of the original. A request with no real lyric lines, or one where the source language can't be worked out, returns 400; more than 200 lines or an overlong line or language code returns 422; if Google is unreachable or rate limited you get 502 or 503. A transient failure isn't cached, but the same lines failing to parse three times over is remembered as a negative and short-circuits to 502 for about a month so the upstream isn't hammered.

Formats

The format field tells you how to read lyrics, and syncType tells you how precise the timing is.

  • ttml is timed XML, the richest format. Pair it with syncType richsync for syllable-level timing or linesync for per-line timing.
  • lrc is timestamped lines, the classic karaoke format.
  • plain is unsynced text, with syncType plain.

Errors and limits

Failed /lyrics calls keep the envelope with success: false plus a code and a human-readable error and hint. Common ones are 404 when nothing matches and 400 when the query is missing a required param. Stay under 120 requests per minute per IP or you get 429.

{
  "success": false,
  "error": "Not found",
  "code": "NOT_FOUND",
  "hint": "No lyrics matched that lookup yet."
}