Images API and image host
The images endpoints list the posters, backdrops, logos, profiles and stills of a title as file paths. You then load 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.
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.
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"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);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
$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);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.
{
"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
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.
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 and 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 and companies and 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 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_pathin your database and build the URL at render time. A new size or host then costs one code change. - Pass
include_image_languagewith your display language plusnull, for exampleru,null, so posters with foreign text do not appear. - An empty array is a normal 200. Fall back to the
poster_pathon the movie details record. - Use
w1280for full width hero backdrops andw780for cards.originalis the stored file at full resolution and is much larger thanw780. - Edge copies live for 30 days and are shared, so a file that one client requested is served from cache to the next.