# Response format

The default TTML provider returns lyrics as Timed Text Markup Language, with syllable-level timing.

## Response structure

```json
{
  "ttml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
  "score": 0.97
}
```

| Field | Type | Description |
| --- | --- | --- |
| `ttml` | string | The full TTML XML document as a string |
| `score` | number | Match confidence from `0` to `1`. Higher is better. Sent only when the lyrics were fetched for the request (or for a matching request already in progress), not on cache hits. |

## TTML structure

The TTML document contains timing information for each word. For the full specification, see the [W3C TTML1 Recommendation](https://www.w3.org/TR/ttml1/).

The example below is formatted for readability. Real responses are not formatted. See [whitespace handling](https://docs.betterlyrics.org/docs/response-format#important-whitespace-handling).

```xml
<?xml version="1.0" encoding="UTF-8"?>
<tt xmlns="http://www.w3.org/ns/ttml">
  <body>
    <div>
      <p begin="00:00:18.234" end="00:00:21.891">
        <span begin="00:00:18.234" end="00:00:18.567">The </span>
        <span begin="00:00:18.567" end="00:00:18.901">club </span>
        <span begin="00:00:18.901" end="00:00:19.234">isn't </span>
        <span begin="00:00:19.234" end="00:00:19.568">the </span>
        <span begin="00:00:19.568" end="00:00:19.901">best </span>
        <span begin="00:00:19.901" end="00:00:20.234">place </span>
        <span begin="00:00:20.234" end="00:00:20.568">to </span>
        <span begin="00:00:20.568" end="00:00:20.901">find </span>
        <span begin="00:00:20.901" end="00:00:21.234">a </span>
        <span begin="00:00:21.234" end="00:00:21.891">lover</span>
      </p>
    </div>
  </body>
</tt>
```

## Key elements

### `<p>` - Line element

Each `<p>` represents a line of lyrics:

-   `begin` - Start time of the line
-   `end` - End time of the line

### `<span>` - Word element

Each `<span>` within a line represents a word:

-   `begin` - Start time of the word
-   `end` - End time of the word
-   Text content is the word itself (including trailing spaces)

## Important: whitespace handling

**Do not format or pretty-print the TTML response.**

The response uses whitespace between `<span>` elements to represent spaces between syllables/words. This whitespace is not enclosed in tags. If you format the XML (for example with a pretty-printer), you lose these spaces and break the syllable sync.

Keep the original string exactly as received. When parsing, use the raw `textContent` of each span, which includes the intended whitespace.

## Time format

Times can appear in two formats:

-   `HH:MM:SS.mmm` - e.g., `00:01:30.500` = 90.5 seconds
-   `M:SS.mmm` - e.g., `1:16.656` = 76.656 seconds

Your parser should handle both. The JavaScript example below does.

## Parsing example (JavaScript)

[`@braccato/parsers`](https://docs.betterlyrics.org/braccato/parsers) is the parser Better Lyrics uses for these responses. It handles background vocals, agents, transliterations and whitespace for you. The example below shows how to do it by hand.

```javascript
function parseTTML(ttmlString) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(ttmlString, 'text/xml');
  const lines = [];

  doc.querySelectorAll('p').forEach(p => {
    const agent = p.getAttribute('ttm:agent'); // Agent ID as defined in metadata (v1, v2, v1000, etc.)
    const words = [];

    // Recursively collect spans, tracking background vocal context
    function collectSpans(element, isBackground = false) {
      element.childNodes.forEach(node => {
        if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'span') {
          const isBg = isBackground || node.getAttribute('ttm:role') === 'x-bg';
          const begin = node.getAttribute('begin');

          if (begin) {
            // This is a timed span (word)
            words.push({
              text: node.textContent,
              begin: parseTime(begin),
              end: parseTime(node.getAttribute('end')),
              isBackground: isBg
            });
          } else {
            // This is a wrapper span, recurse into it
            collectSpans(node, isBg);
          }
        }
      });
    }

    collectSpans(p);

    lines.push({
      begin: parseTime(p.getAttribute('begin')),
      end: parseTime(p.getAttribute('end')),
      agent,
      words
    });
  });

  return lines;
}

function parseTime(timeStr) {
  // Handles "HH:MM:SS.mmm", "M:SS.mmm", "SS.mmm"
  const parts = timeStr.split(':');
  let seconds = 0;

  if (parts.length === 3) {
    seconds = parseInt(parts[0]) * 3600 +
              parseInt(parts[1]) * 60 +
              parseFloat(parts[2]);
  } else if (parts.length === 2) {
    seconds = parseInt(parts[0]) * 60 +
              parseFloat(parts[1]);
  } else {
    seconds = parseFloat(parts[0]);
  }

  return Math.round(seconds * 1000); // Return milliseconds
}
```

## RTL language support

For right-to-left languages (Arabic, Hebrew, Persian, etc.), the TTML document includes language attributes:

```xml
<tt xml:lang="ar" xmlns="http://www.w3.org/ns/ttml">
  ...
</tt>
```

Check the `xml:lang` attribute to determine text direction. Common RTL language codes: `ar`, `he`, `fa`, `ur`.

## Multiple speakers

Songs with multiple vocalists use the `ttm:agent` attribute on `<p>` elements to identify which speaker is singing each line:

```xml
<p ttm:agent="v1" begin="0:05.123" end="0:08.456">
  <span begin="0:05.123" end="0:06.789">First </span>
  <span begin="0:06.789" end="0:08.456">vocalist</span>
</p>
<p ttm:agent="v2" begin="0:08.456" end="0:11.789">
  <span begin="0:08.456" end="0:10.123">Second </span>
  <span begin="0:10.123" end="0:11.789">vocalist</span>
</p>
```

Agent IDs are defined in the TTML metadata header:

```xml
<head>
  <metadata>
    <ttm:agent type="person" xml:id="v1">
      <ttm:name type="full">Artist Name</ttm:name>
    </ttm:agent>
    <ttm:agent type="person" xml:id="v2">
      <ttm:name type="full">Featured Artist</ttm:name>
    </ttm:agent>
  </metadata>
</head>
```

-   Agent IDs are defined via `<ttm:agent xml:id="...">` in the metadata
-   Most files use `v1`, `v2`, etc., but some use different IDs like `voice1`
-   `v1000` (group) and `v2000` (other) conventionally indicate multiple speakers singing together
-   Agent metadata may include speaker names via `<ttm:name>`
-   The `type` attribute must be one of: `person`, `character`, `group`, `organization`, or `other`

## Background vocals

Background vocals within a line are wrapped in a `<span>` with `ttm:role="x-bg"`:

```xml
<p ttm:agent="v1" begin="0:15.000" end="0:20.000">
  <span begin="0:15.000" end="0:17.500">Lead lyrics </span>
  <span ttm:role="x-bg">
    <span begin="0:17.500" end="0:20.000">background vocals</span>
  </span>
</p>
```

## Transliterations

Some TTML responses include synced transliterations (e.g., romanized Japanese lyrics). These are stored in the metadata header under `<transliterations>`:

```xml
<head>
  <metadata>
    <transliterations>
      <transliteration xml:lang="ja-Latn">
        <text for="L10">
          <span begin="1:03.557" end="1:04.868">mayonaka no</span>
          <span begin="1:04.868" end="1:05.457">doa o</span>
          <span begin="1:05.457" end="1:05.930">tata</span>
          <span begin="1:05.930" end="1:07.907">ki</span>
        </text>
      </transliteration>
    </transliterations>
  </metadata>
</head>
```

-   The `xml:lang` attribute specifies the target script (e.g., `ja-Latn` for Japanese romanization)
-   Each `<text for="...">` references a line by its `itunes:key` attribute
-   Contains timed `<span>` elements, same as the main lyrics
-   Note: Some transliterations have punctuation (like `)`) outside of `<span>` elements as raw text nodes

## Response headers

These response headers add context:

| Header | Description |
| --- | --- |
| `X-Cache-Status` | Whether the response came from cache (HIT, MISS, etc.) |
| `X-Provider` | Which provider served the response. Only on the provider-specific endpoints, such as `/ttml/getLyrics`. `/getLyrics` does not send it. |
| `X-RateLimit-Type` | Current rate limit tier |

Source: https://docs.betterlyrics.org/docs/response-format
