# Images API and the TMDB image path

> The images endpoints list the posters, backdrops, logos, profiles and stills of a title as file paths. The TMDB image path scheme then loads each file from img.amodb.dev in a fixed size, with no key and a 30 day edge cache. Paths and JSON match TMDB v3, so existing image code works after a base URL change.

## Endpoint

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

```bash
curl "https://api.amodb.dev/3/movie/550/images?include_image_language=en,null" \
  -H "Authorization: Bearer $AMODB_KEY"
```

Response:

```json
{
  "id": 550,
  "backdrops": [
    {
      "aspect_ratio": 1.778,
      "height": 1440,
      "iso_639_1": null,
      "file_path": "/c6OLXfKAk5BKeR6broC8pYiCquX.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2560
    },
    {
      "aspect_ratio": 1.778,
      "height": 1080,
      "iso_639_1": "en",
      "file_path": "/b9HyPoxwxjxkWEUL5ErZdhApQe2.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1920
    },
    {
      "aspect_ratio": 1.778,
      "height": 2160,
      "iso_639_1": "en",
      "file_path": "/ewMhHVyXQKMAviVWM7InPjZqpSr.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 3840
    }
  ],
  "logos": [
    {
      "aspect_ratio": 4.638,
      "height": 389,
      "iso_639_1": "en",
      "file_path": "/7Uqhv24pGJs4Ns31NoOPWFJGWNG.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1804
    },
    {
      "aspect_ratio": 0.848,
      "height": 1295,
      "iso_639_1": "en",
      "file_path": "/4XkF0Rf7gvSfea8fYLFbU5tmuJw.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1098
    },
    {
      "aspect_ratio": 1.457,
      "height": 1299,
      "iso_639_1": "en",
      "file_path": "/lm0VCzO1ZFhyx81k5Fzkex4u6C8.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1893
    }
  ],
  "posters": [
    {
      "aspect_ratio": 0.667,
      "height": 3000,
      "iso_639_1": "en",
      "file_path": "/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2000
    },
    {
      "aspect_ratio": 0,
      "height": 0,
      "iso_639_1": null,
      "file_path": "/qkrH7rjYKU4KOwwIl6wF6H3ISvm.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 0
    },
    {
      "aspect_ratio": 0,
      "height": 0,
      "iso_639_1": null,
      "file_path": "/efBb4gjjKneUoBVgfFOUu2OwihS.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 0
    }
  ]
}
```

## Get images for a movie

`GET /3/movie/{movie_id}/images` lists every known image of one movie as three arrays: `backdrops`, `logos` and `posters`. Each entry has a `file_path`, its pixel size and a language tag. The file itself is not in the JSON. You load it from the image host in a second step.

The example asks for English and textless images, picks the first English poster and then fetches that file at 500 pixels wide.

```bash
curl -s "https://api.amodb.dev/3/movie/550/images?include_image_language=en,null" \
  -H "Authorization: Bearer $AMODB_KEY"

curl -s -o poster.jpg -w "%{http_code}\n" \
  "https://img.amodb.dev/t/p/w500/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg"
```

```js
const url = 'https://api.amodb.dev/3/movie/550/images?include_image_language=en,null';
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AMODB_KEY}` } });
if (!res.ok) throw new Error(`amodb returned ${res.status}`);
const { posters } = await res.json();
if (posters.length === 0) throw new Error('no posters');
const poster = posters.find((p) => p.iso_639_1 === 'en') ?? posters[0];
const posterUrl = `https://img.amodb.dev/t/p/w500${poster.file_path}`;
const img = await fetch(posterUrl);
console.log(img.status, img.headers.get('content-type'), posterUrl);
```

```python
import os
import requests

r = requests.get(
    "https://api.amodb.dev/3/movie/550/images",
    params={"include_image_language": "en,null"},
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
r.raise_for_status()
posters = r.json()["posters"]
if not posters:
    raise SystemExit("no posters")
poster = next((p for p in posters if p["iso_639_1"] == "en"), posters[0])
poster_url = "https://img.amodb.dev/t/p/w500" + poster["file_path"]
img = requests.get(poster_url, timeout=10)
print(img.status_code, img.headers.get("content-type"), poster_url)
```

```php
<?php
$url = 'https://api.amodb.dev/3/movie/550/images?include_image_language=en,null';
$ch = curl_init($url);
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");
}
$posters = json_decode($body, true)['posters'];
if (!$posters) {
    throw new RuntimeException('no posters');
}
$poster = $posters[0];
foreach ($posters as $p) {
    if ($p['iso_639_1'] === 'en') {
        $poster = $p;
        break;
    }
}
$posterUrl = 'https://img.amodb.dev/t/p/w500' . $poster['file_path'];
$img = curl_init($posterUrl);
curl_setopt_array($img, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10]);
curl_exec($img);
echo curl_getinfo($img, CURLINFO_HTTP_CODE), ' ', curl_getinfo($img, CURLINFO_CONTENT_TYPE), ' ', $posterUrl, PHP_EOL;
curl_close($img);
```

```go
package main

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

type Images struct {
	Posters []struct {
		FilePath string `json:"file_path"`
		Language string `json:"iso_639_1"`
	} `json:"posters"`
}

func main() {
	url := "https://api.amodb.dev/3/movie/550/images?include_image_language=en,null"
	req, _ := http.NewRequest("GET", url, 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 images Images
	if err := json.NewDecoder(res.Body).Decode(&images); err != nil || len(images.Posters) == 0 {
		panic("no posters")
	}
	poster := images.Posters[0]
	for _, p := range images.Posters {
		if p.Language == "en" {
			poster = p
			break
		}
	}
	posterURL := "https://img.amodb.dev/t/p/w500" + poster.FilePath
	img, err := client.Get(posterURL)
	if err != nil {
		panic(err)
	}
	defer img.Body.Close()
	fmt.Println(img.StatusCode, img.Header.Get("Content-Type"), posterURL)
}
```

### Parameters

| Parameter                | In    | Required   | Description                                                                                                                                                            |
| ------------------------ | ----- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `movie_id`               | path  | yes        | Numeric movie id, for example `550`.                                                                                                                                   |
| `api_key`                | query | one of two | Your key. Use this or the `Authorization: Bearer` header.                                                                                                              |
| `include_image_language` | query | no         | Comma separated ISO 639-1 codes. Add `null` to include textless images, for example `en,null`. Only the listed languages come back. Without it you get every language. |
| `language`               | query | no         | Accepted but ignored on this endpoint. Use `include_image_language` to pick languages.                                                                                 |

### Response fields

| Field                           | Type            | Notes                                                                                                                     |
| ------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `id`                            | integer         | The movie id.                                                                                                             |
| `backdrops`, `logos`, `posters` | array           | One array per image kind, in that key order. Each can be empty.                                                           |
| `file_path`                     | string          | Starts with a slash. Append it to a size on the image host. Posters and backdrops are `.jpg`, logos are `.png` or `.svg`. |
| `width`, `height`               | integer         | Pixel size of the original file. `0` when the size is not known.                                                          |
| `aspect_ratio`                  | number          | Width divided by height. Posters sit near 0.667, backdrops near 1.778. `0` when the size is not known.                    |
| `iso_639_1`                     | string or null  | Language of the text on the image. `null` means no text.                                                                  |
| `vote_average`, `vote_count`    | number, integer | Always `0` for movie and series images, so they cannot rank images. Keep the order the API returns.                       |

### Example response

Two entries per array are shown.

```json
{
  "id": 550,
  "backdrops": [
    {
      "aspect_ratio": 1.778,
      "height": 1440,
      "iso_639_1": null,
      "file_path": "/c6OLXfKAk5BKeR6broC8pYiCquX.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2560
    },
    {
      "aspect_ratio": 1.778,
      "height": 1080,
      "iso_639_1": "en",
      "file_path": "/b9HyPoxwxjxkWEUL5ErZdhApQe2.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1920
    }
  ],
  "logos": [
    {
      "aspect_ratio": 4.638,
      "height": 389,
      "iso_639_1": "en",
      "file_path": "/7Uqhv24pGJs4Ns31NoOPWFJGWNG.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1804
    },
    {
      "aspect_ratio": 0.848,
      "height": 1295,
      "iso_639_1": "en",
      "file_path": "/4XkF0Rf7gvSfea8fYLFbU5tmuJw.png",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1098
    }
  ],
  "posters": [
    {
      "aspect_ratio": 0.667,
      "height": 3000,
      "iso_639_1": "en",
      "file_path": "/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2000
    },
    {
      "aspect_ratio": 0,
      "height": 0,
      "iso_639_1": null,
      "file_path": "/qkrH7rjYKU4KOwwIl6wF6H3ISvm.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 0
    }
  ]
}
```

The second poster shows an entry whose size is not known. The file still loads.

## Build an image URL

This is the same scheme as the TMDB image path: `https://img.amodb.dev/t/p/{size}/{file}`. The `{size}` comes from the list below, and `{file}` is the value of `poster_path` or `backdrop_path` from a details or list record. Code that builds a TMDB poster URL keeps working after you change the base URL. The [movie poster API](https://amodb.dev/movie-poster-api/) page goes further on poster sizes and `srcset`.

Join three parts: `https://img.amodb.dev/t/p/`, a size segment and the `file_path`. The path already starts with a slash, so `https://img.amodb.dev/t/p/w500` plus `/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg` is a complete URL.

A size is `w` plus a width in pixels, `h` plus a height in pixels, or `original` for the stored file. The host serves ten sizes: `w92`, `w154`, `w185`, `w300`, `w342`, `w500`, `w780`, `w1280`, `h632` and `original`. Any other size, such as `w400`, answers 404. `/3/configuration` lists the sizes that fit each image kind.

| Kind     | Config field     | Sizes                                                     |
| -------- | ---------------- | --------------------------------------------------------- |
| Poster   | `poster_sizes`   | `w92`, `w154`, `w185`, `w342`, `w500`, `w780`, `original` |
| Backdrop | `backdrop_sizes` | `w300`, `w780`, `w1280`, `original`                       |
| Logo     | `logo_sizes`     | `w92`, `w154`, `w185`, `w300`, `w500`, `original`         |
| Profile  | `profile_sizes`  | `w185`, `h632`, `original`                                |
| Still    | `still_sizes`    | `w92`, `w185`, `w300`, `original`                         |

The host does not check the kind, so a poster also loads at `w1280`. Stay with the sizes listed for the kind, so your code matches the configuration and any TMDB client.

The image host needs no key, and the edge keeps each file for 30 days. Because no key is sent, image loads do not use your requests per minute. Responses carry `Access-Control-Allow-Origin: *`, so a browser can load them from any page. An unknown size or an unknown file answers 404 with a JSON body.

## Responsive images with srcset

Pick the smallest size that still covers the displayed width times the screen density. A poster shown 171 CSS pixels wide needs `w342` on a 2x screen. This helper builds a `srcset` from a stored path.

```js
const WIDTHS = [185, 342, 500, 780];

function posterSrcset(filePath) {
  return WIDTHS.map((w) => `https://img.amodb.dev/t/p/w${w}${filePath} ${w}w`).join(', ');
}

const path = '/jSziioSwPVrOy9Yow3XhWIBDjq1.jpg';
console.log(`<img src="https://img.amodb.dev/t/p/w342${path}" srcset="${posterSrcset(path)}" sizes="(max-width: 600px) 50vw, 342px" alt="Fight Club poster">`);
```

## Other image endpoints

All paths below use the base `https://api.amodb.dev/3` and return the arrays named in the second column.

| Method and path                                                              | Arrays returned                                        | Parameters               |
| ---------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------ |
| `GET /tv/{series_id}/images`                                                 | `backdrops`, `logos`, `posters`                        | `include_image_language` |
| `GET /tv/{series_id}/season/{season_number}/images`                          | `posters`, always empty                                | none                     |
| `GET /tv/{series_id}/season/{season_number}/episode/{episode_number}/images` | `stills`, one entry with `width` and `height` set to 0 | none                     |
| `GET /person/{person_id}/images`                                             | `profiles`                                             | none                     |

For movies and series you can also add `append_to_response=images` to a details call. `include_image_language` works there too. The appended block has no `id` of its own.

Person profiles are the one place where `vote_average` carries a real score. The `id` of a season or episode response is the season or episode id. See [people](https://amodb.dev/people/) and [TV series](https://amodb.dev/tv-series/) for the parent records.

amodb does not offer images paths for collections, companies and networks. Their records already hold one `poster_path`, `backdrop_path` or `logo_path`, described on [collections](https://amodb.dev/collections/) and [companies and networks](https://amodb.dev/companies-networks/).

## The configuration endpoint

`GET /3/configuration` returns an `images` block with `secure_base_url` set to `https://img.amodb.dev/t/p/`, `base_url` (the plain http form), and the size lists `backdrop_sizes`, `logo_sizes`, `poster_sizes`, `profile_sizes` and `still_sizes`. Read it once at startup, use the secure form, and build URLs from it instead of hard coding sizes. The edge caches it for 24 hours.

## Errors

| HTTP | status_code | When                                                                                     |
| ---- | ----------- | ---------------------------------------------------------------------------------------- |
| 401  | 7           | The key is missing or wrong on a JSON call.                                              |
| 402  | 36          | The plan period has ended.                                                               |
| 404  | 34          | No person, season or episode with that id, or an unknown size or file on the image host. |
| 405  | 37          | You used a write method on an images path.                                               |
| 429  | 25          | You went over your requests per minute. Wait for the `Retry-After` seconds.              |

A movie or series id that does not exist is not an error here: the images path answers 200 with empty arrays, while the details path answers 404. See [errors](https://amodb.dev/errors/) for the full list. Image file requests are plain HTTP loads, so check the status code of the response instead of parsing a JSON body.

## Practical notes

- Store `file_path` in your database and build the URL at render time. A new size or host then costs one code change.
- Pass `include_image_language` with your display language plus `null`, for example `ru,null`, so posters with foreign text do not appear.
- An empty array is a normal 200. Fall back to the `poster_path` on the [movie details](https://amodb.dev/movies/) record.
- Use `w1280` for full width hero backdrops and `w780` for cards. `original` is the stored file at full resolution and is much larger than `w780`.
- Edge copies live for 30 days and are shared, so a file that one client requested is served from cache to the next.

## Questions and answers

### Do image requests need an API key?

No. Files at img.amodb.dev/t/p/{size}/{file} are public, so you can use them directly in img tags and CSS. Only the JSON endpoints that list file paths need your key.

### Which image sizes can I request?

w92, w154, w185, w300, w342, w500, w780, w1280, h632 and original. Any other size answers 404. /3/configuration lists which of them fit each image type, and the size table on this page repeats it.

### How do I get posters in one language only?

Pass include_image_language with a comma separated list, for example en,null. The value null adds textless images. Only the listed languages come back. The language parameter does not filter images.

### Why does the file path start with a slash?

TMDB style paths include the leading slash so you can append them straight to the size segment. Store the path alone and build the full URL when you render.

### What is the TMDB image path?

It is the scheme TMDB uses to build image URLs, and amodb uses the same one: https://img.amodb.dev/t/p/{size}/{file}. Pick the size from w92, w154, w185, w300, w342, w500, w780, w1280, h632 or original. The file is the poster_path or backdrop_path value of a title. No key is needed. The movie poster API page covers poster sizes and srcset.

## 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, the TV show endpoint](https://amodb.dev/tv-series/): TV show API reference for GET /3/tv/{series_id}: names, status, seasons, networks and the next episode, in TMDB v3 JSON with content ratings and providers.
- [Person details API](https://amodb.dev/people/): Person details API with the same paths and JSON as TMDB v3. Get an actor or crew member by id, with biography, filmography, external ids and the popular list.
- [Collections API](https://amodb.dev/collections/): Movie collections API with TMDB v3 paths: fetch a franchise by id with all its parts, read belongs_to_collection on a movie, and search collections by name.
- [Movie poster API and image CDN](https://amodb.dev/movie-poster-api/): Movie poster API with a keyless image host. Pick sizes from w92 to original, build srcset lists and choose posters by language with include_image_language.
- [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-19. Source: https://amodb.dev/images-api/
