# Soundtracks

> GET /3/movie/{movie_id}/soundtracks and GET /3/tv/{tv_id}/soundtracks return the soundtrack album of a title in a results list. Each album has a source, name, artist, release date, link and a tracks list with disc, number, title, artist and duration. Add append_to_response=soundtracks to merge it into details.

## Endpoint

`GET /3/movie/{movie_id}/soundtracks`

```bash
curl "https://api.amodb.dev/3/movie/155/soundtracks" \
  -H "Authorization: Bearer $AMODB_KEY"
```

Response:

```json
{
  "id": 155,
  "results": [
    {
      "source": "musicbrainz",
      "album": "The Dark Knight",
      "artist": "Hans Zimmer & James Newton Howard",
      "released": "2008-07-15",
      "url": "https://musicbrainz.org/release/814b2f7b-c27c-451e-abac-a316d2e6cea9",
      "track_count": 14,
      "tracks": [
        {
          "disc": 1,
          "number": 1,
          "title": "Why So Serious?",
          "artist": "Hans Zimmer",
          "duration_ms": 554000
        },
        {
          "disc": 1,
          "number": 2,
          "title": "I'm Not a Hero",
          "artist": "Hans Zimmer",
          "duration_ms": 395000
        },
        {
          "disc": 1,
          "number": 3,
          "title": "Harvey Two-Face",
          "artist": "James Newton Howard",
          "duration_ms": 377000
        }
      ]
    }
  ]
}
```

## Get the soundtrack of a movie

`GET /3/movie/{movie_id}/soundtracks` returns the soundtrack album of one movie. The reply carries the movie `id` and a `results` array. Each entry is an album with its details and a `tracks` list.

The sample loads The Dark Knight (id 155) and prints the album and one line per track.

```bash
curl -sf "https://api.amodb.dev/3/movie/155/soundtracks" \
  -H "Authorization: Bearer $AMODB_KEY"
```

```js
const res = await fetch('https://api.amodb.dev/3/movie/155/soundtracks', {
  headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` },
});
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { results } = await res.json();
if (results.length === 0) console.log('no soundtrack album known');
for (const album of results) {
  console.log(`${album.album} by ${album.artist} (${album.track_count} tracks)`);
  for (const t of album.tracks) {
    console.log(`${t.number}. ${t.title} by ${t.artist}`);
  }
}
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/155/soundtracks",
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
results = r.json()["results"]
if not results:
    print("no soundtrack album known")
for album in results:
    print(f"{album['album']} by {album['artist']} ({album['track_count']} tracks)")
    for t in album["tracks"]:
        print(f"{t['number']}. {t['title']} by {t['artist']}")
```

```php
<?php
$ch = curl_init('https://api.amodb.dev/3/movie/155/soundtracks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('AMODB_KEY')],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
    throw new RuntimeException("amodb returned $status");
}
$results = json_decode($body, true)['results'];
if (!$results) {
    echo 'no soundtrack album known', PHP_EOL;
}
foreach ($results as $album) {
    echo $album['album'], ' by ', $album['artist'] ?? 'unknown', ' (', $album['track_count'], ' tracks)', PHP_EOL;
    foreach ($album['tracks'] as $t) {
        echo $t['number'], '. ', $t['title'], ' by ', $t['artist'], PHP_EOL;
    }
}
```

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"time"
)

type Track struct {
	Disc       int    `json:"disc"`
	Number     int    `json:"number"`
	Title      string `json:"title"`
	Artist     string `json:"artist"`
	DurationMs int    `json:"duration_ms"`
}

type Album struct {
	Source     string  `json:"source"`
	Album      string  `json:"album"`
	Artist     string  `json:"artist"`
	Released   string  `json:"released"`
	URL        string  `json:"url"`
	TrackCount int     `json:"track_count"`
	Tracks     []Track `json:"tracks"`
}

func main() {
	req, _ := http.NewRequest("GET", "https://api.amodb.dev/3/movie/155/soundtracks", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AMODB_KEY"))
	client := &http.Client{Timeout: 10 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		panic(res.Status)
	}
	var body struct {
		Results []Album `json:"results"`
	}
	if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
		panic(err)
	}
	if len(body.Results) == 0 {
		fmt.Println("no soundtrack album known")
	}
	for _, a := range body.Results {
		fmt.Printf("%s by %s (%d tracks)\n", a.Album, a.Artist, a.TrackCount)
		for _, t := range a.Tracks {
			fmt.Printf("%d. %s by %s\n", t.Number, t.Title, t.Artist)
		}
	}
}
```

### Parameters

| Parameter  | In    | Required   | Description                                               |
| ---------- | ----- | ---------- | --------------------------------------------------------- |
| `movie_id` | path  | yes        | Numeric movie id, for example `155`.                      |
| `api_key`  | query | one of two | Your key. Use this or the `Authorization: Bearer` header. |

### Response fields

| Field                            | Type           | Notes                                                             |
| -------------------------------- | -------------- | ----------------------------------------------------------------- |
| `id`                             | integer        | Movie id. Not present when you use `append_to_response`.          |
| `results`                        | array          | Albums. Empty when no album is known. Today it holds at most one. |
| `results[].source`               | string         | Where the album comes from: `musicbrainz` or `deezer`.            |
| `results[].album`                | string         | Album title.                                                      |
| `results[].artist`               | string or null | Album artist, for example `Hans Zimmer & James Newton Howard`.    |
| `results[].released`             | string or null | Release date as `YYYY-MM-DD`.                                     |
| `results[].url`                  | string or null | Link to the album page at the source.                             |
| `results[].track_count`          | integer        | Number of tracks on the album.                                    |
| `results[].tracks`               | array          | Tracks in album order.                                            |
| `results[].tracks[].disc`        | integer        | Disc number, starting at 1.                                       |
| `results[].tracks[].number`      | integer        | Track number on the disc.                                         |
| `results[].tracks[].title`       | string         | Track title.                                                      |
| `results[].tracks[].artist`      | string         | Track artist.                                                     |
| `results[].tracks[].duration_ms` | integer        | Length in milliseconds.                                           |

Read the keys you use by name and skip other keys.

### Example response

```json
{
  "id": 155,
  "results": [
    {
      "source": "musicbrainz",
      "album": "The Dark Knight",
      "artist": "Hans Zimmer & James Newton Howard",
      "released": "2008-07-15",
      "url": "https://musicbrainz.org/release/814b2f7b-c27c-451e-abac-a316d2e6cea9",
      "track_count": 14,
      "tracks": [
        { "disc": 1, "number": 1, "title": "Why So Serious?", "artist": "Hans Zimmer", "duration_ms": 554000 },
        { "disc": 1, "number": 2, "title": "I'm Not a Hero", "artist": "Hans Zimmer", "duration_ms": 395000 },
        { "disc": 1, "number": 3, "title": "Harvey Two-Face", "artist": "James Newton Howard", "duration_ms": 377000 }
      ]
    }
  ]
}
```

The track list is cut to 3 of 14. A title with no album known, such as Fight Club (id 550), answers with an empty list:

```json
{
  "id": 550,
  "results": []
}
```

## Get the soundtrack of a series

`GET /3/tv/{tv_id}/soundtracks` returns the same shape for a series. Pass the series id, for example `1396`. Breaking Bad has no album known, so its `results` array is empty.

| Method and path                     | Returns                            | Parameters                   |
| ----------------------------------- | ---------------------------------- | ---------------------------- |
| `GET /movie/{movie_id}/soundtracks` | `id` and `results`                 | `movie_id`                   |
| `GET /tv/{tv_id}/soundtracks`       | `id` and `results`                 | `tv_id`                      |
| `append_to_response=soundtracks`    | Same `results` under `soundtracks` | Added to movie or TV details |

To get the album while loading a movie page, append it to details:

```bash
curl -sf "https://api.amodb.dev/3/movie/155?append_to_response=credits,soundtracks" \
  -H "Authorization: Bearer $AMODB_KEY"
```

The appended block sits under the `soundtracks` key of the record. It holds `results` and has no `id` of its own. It counts as one request.

## Build a music in this movie list

Sort by `disc` and `number`, show the title and the artist, and hide the block when `results` is empty. The functions below return plain text lines that you can put in a list element.

```js
function clock(ms) {
  const s = Math.round(ms / 1000);
  return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
}

function musicLines(results) {
  const album = results[0];
  if (!album) return [];
  return [...album.tracks]
    .sort((a, b) => a.disc - b.disc || a.number - b.number)
    .map((t) => `${t.title} by ${t.artist} (${clock(t.duration_ms)})`);
}
```

Some interface ideas that need no extra data:

| Idea          | How                                                                                |
| ------------- | ---------------------------------------------------------------------------------- |
| Numbered list | Use `number` as the list number, and group by `disc` when there are several discs. |
| Album link    | Link the album name to `url` when it is not null.                                  |
| Total length  | Add up `duration_ms` over the tracks.                                              |
| Mood pairing  | Show the list beside [mood meters](https://amodb.dev/moods/) of the same title.    |

## Errors

Every failure returns `success` as false with a numeric `status_code`, as TMDB does.

| HTTP | status_code | When                                                               |
| ---- | ----------- | ------------------------------------------------------------------ |
| 401  | 7           | The key is missing or wrong.                                       |
| 402  | 36          | The plan period has ended.                                         |
| 429  | 25          | Over your requests per minute. Wait for the `Retry-After` seconds. |

The soundtracks endpoint does not answer 404 for an unknown id. It answers 200 with an empty `results` array. Appended to details, an unknown id is a 404 from the details call. See [errors](https://amodb.dev/errors/) for every code and [rate limits](https://amodb.dev/rate-limits/) for the headers.

## Practical notes

- Like other detail data, soundtracks are cached at the edge for 6 hours. A shorter client cache gains nothing.
- Use `append_to_response=soundtracks` on a details page you already load. It saves a request of your 20, 40 or 120 requests per minute.
- Do not require an album to exist. Hide the music block when `results` is empty.
- Some albums come from MusicBrainz and others from Deezer. Movie 27205 (Inception) has a Deezer album. The shape is the same, so check `source` only if you show it.
- Track and artist names are plain strings and can contain any character. Store them as UTF-8 and escape them when you place them in HTML.
- For the trailer of the same title, see [videos and trailers](https://amodb.dev/videos-trailers/).

## Questions and answers

### What does one soundtrack entry contain?

One album object. It has the source, album name, artist, release date, a link and track_count. Its tracks array lists each track with disc, number, title, artist and duration_ms. The reply also carries the title id.

### Do I get audio files or streaming links?

No audio. The url field links to the album page on MusicBrainz or Deezer, and it can be null. To let users listen, search for the album in the music service you already use and open it from your own interface.

### Is there a soundtrack for every title?

No. Many titles, Fight Club among them, have no album known, and the reply is an empty results array. Today a title has at most one album. Design the music block to hide itself when there is nothing to show.

### Can I get soundtracks for a series?

Yes. Call GET /3/tv/{tv_id}/soundtracks with the series id. The reply has the same shape as the movie endpoint, so one parser covers both.

## Related pages

- [Movie details API](https://amodb.dev/movies/): Movie details API with the same paths and JSON as TMDB v3. Get one movie by id, add credits, ratings and moods with append_to_response, in 14 languages.
- [TV series API](https://amodb.dev/tv-series/): TV series API with the same paths and JSON as TMDB v3. Get a show by id with seasons, networks and the next episode, plus content ratings and watch providers.
- [Mood meters](https://amodb.dev/moods/): Movie mood API with 0 to 10 mood meters per title. Read moods for a movie or series, merge them with append_to_response=moods and build badges or filters.
- [Videos and trailers API](https://amodb.dev/videos-trailers/): Movie videos API with TMDB v3 paths: list trailers, teasers, clips and featurettes for a movie or series, then filter by type and language in your code.
- [Movie and TV ratings](https://amodb.dev/ratings/): Movie ratings API that returns IMDb, Kinopoisk, Metacritic, Rotten Tomatoes and TMDB scores for a title in one call, with null for any source not known.
- [Quickstart](https://amodb.dev/quickstart/): Make your first amodb call: get a key, request movie details in curl, JavaScript, Python, PHP or Go, then load the poster from img.amodb.dev. Under 2 minutes.

Updated: 2026-10-18. Source: https://amodb.dev/soundtracks/
