Movie posters from an image host that needs no key

A poster URL is https://img.amodb.dev/t/p/{size}/{file}. The file part is the poster_path string from any movie, search or list reply, and it starts with a slash. The size part is a name such as w342. Images need no key, so browsers and apps can load them directly. The JSON that tells you the file name still needs your key, and amodb is an independent API that speaks the TMDB v3 protocol, so existing image code keeps working after a host change.

What you get:

  • Posters, backdrops, logos, profile photos and episode stills on one host.
  • Ten size names, from w92 to original.
  • Poster lists per title, with language, width and height on each item.
  • A 30 day edge cache for images.

What data you get

ImageFile name comes fromCommon sizes
Movie posterposter_path on /3/movie/{movie_id} and in listsw92, w154, w185, w342, w500, w780, original
Backdropbackdrop_pathw300, w780, w1280, original
Person photoprofile_path on /3/person/{person_id}w185, h632, original
Company or network logologo_pathw92, w154, w185, w300, w500, original
Episode stillstill_pathw92, w185, w300, original
All posters of one titleGET /3/movie/{movie_id}/imagesposters array

TV series, seasons and collections carry poster_path too. Read the exact size lists from the images block of GET /3/configuration once at startup instead of hardcoding them. The images reference is on images.

Start in 3 steps

  1. Choose a plan on pricing. It sets the rate of the JSON calls: 20, 40 or 120 requests per minute.
  2. Create a key in your account dashboard.
  3. Read poster_path from a details or list call, then put it behind the image host and a size name.

Example request

The Go program lists the posters of The Matrix (movie id 603) in English and without text, picks the widest one, prints a ready srcset and saves the w342 file. The key goes to the JSON call only.

bashgo
# the JSON call needs the key
curl -s "https://api.amodb.dev/3/movie/603/images?include_image_language=en,null" \
  -H "Authorization: Bearer $AMODB_KEY"

# the image call does not
curl -s -o poster.jpg "https://img.amodb.dev/t/p/w342/aOIuZAjPaRIE6CMzbazvcHuHXDc.jpg"
package main

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

const imgBase = "https://img.amodb.dev/t/p/"

type poster struct {
	FilePath string  `json:"file_path"`
	Lang     *string `json:"iso_639_1"`
	Width    int     `json:"width"`
}

type images struct {
	Posters []poster `json:"posters"`
}

func srcset(file string) string {
	var parts []string
	for _, w := range []int{185, 342, 500, 780} {
		parts = append(parts, fmt.Sprintf("%sw%d%s %dw", imgBase, w, file, w))
	}
	return strings.Join(parts, ", ")
}

func main() {
	client := &http.Client{Timeout: 10 * time.Second}

	url := "https://api.amodb.dev/3/movie/603/images?include_image_language=en,null"
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AMODB_KEY"))
	res, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode != http.StatusOK {
		panic(res.Status)
	}

	var imgs images
	if err := json.NewDecoder(res.Body).Decode(&imgs); err != nil {
		panic(err)
	}
	if len(imgs.Posters) == 0 {
		fmt.Println("no posters")
		return
	}

	best := imgs.Posters[0]
	for _, p := range imgs.Posters {
		if p.Width > best.Width {
			best = p
		}
	}
	lang := "none"
	if best.Lang != nil {
		lang = *best.Lang
	}
	fmt.Println("best poster:", best.FilePath, "language:", lang, "width:", best.Width)
	fmt.Println("srcset:", srcset(best.FilePath))

	// Image requests carry no key.
	pic, err := client.Get(imgBase + "w342" + best.FilePath)
	if err != nil {
		panic(err)
	}
	defer pic.Body.Close()
	if pic.StatusCode != http.StatusOK {
		panic(pic.Status)
	}
	out, err := os.Create("poster.jpg")
	if err != nil {
		panic(err)
	}
	defer out.Close()
	io.Copy(out, pic.Body)
}

A trimmed images reply for include_image_language=en,null: 3 of the 145 posters, an English one, the widest English one and a textless one. The reply also holds backdrops and logos, left out here.

JSON
{
  "id": 603,
  "posters": [
    {
      "aspect_ratio": 0.667,
      "height": 2250,
      "iso_639_1": "en",
      "file_path": "/dXNAPwY7VrqMAo51EKhhCJfaGb5.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1500
    },
    {
      "aspect_ratio": 0.667,
      "height": 3000,
      "iso_639_1": "en",
      "file_path": "/aOIuZAjPaRIE6CMzbazvcHuHXDc.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 2000
    },
    {
      "aspect_ratio": 0.701,
      "height": 1426,
      "iso_639_1": null,
      "file_path": "/iLWzJxatuwwMT76dtMuwh3FEp2X.jpg",
      "vote_average": 0,
      "vote_count": 0,
      "width": 1000
    }
  ]
}

Choose a size

The number in a w size is the image width in pixels, and h632 is a height. Small names save bandwidth, large names keep detail on dense screens.

SizeTypical use
w92Search suggestions and tiny list rows
w154, w185Dense lists and cards
w342Grid tiles on phones
w500Detail page
w780Large screens and 2x displays
originalArchive or print, the unscaled file

Give the browser several widths and let it choose. The Go program above prints a srcset with 185w, 342w, 500w and 780w candidates. Pair it with a sizes attribute that matches your layout, for example sizes="(max-width: 600px) 45vw, 185px" on the img element.

Poster languages

Each item in posters has an iso_639_1 value, either a language code or null for artwork without text. The include_image_language parameter takes a comma separated list of codes.

ValueReturns
enPosters marked English
en,nullEnglish posters and posters without text
nullOnly posters without text

A good fallback order is the poster in your user's language, then a textless one, then English. Inside each group, sort by width and keep the first on ties. vote_average and vote_count are 0 on movie and TV images, so they rank nothing. Poster languages describe the text printed on the artwork, so they do not follow the 14 interface languages. Check iso_639_1 on what comes back, because the set depends on the title.

Storing and caching posters

  • Save poster_path, not the full URL. Any size can be built from it later.
  • The edge caches images for 30 days. Details are cached for 6 hours, so read poster_path again after that to see whether it changed.
  • Keep the key on your server. Send the browser the image URLs only.
  • When poster_path is null, render a placeholder instead of a broken image.

How it compares

CapabilityScrape pages for imagesHost your own copiesamodb image host
Key neededNone, the terms of each site applyNoneNo key for images
Size variantsWhatever size the page servesYou generate and store each oneFixed size names in the URL, w92 up to original
Finding the fileParse HTMLYour own databaseposter_path in every reply
Artwork languagesPage by pageWhatever you collectedinclude_image_language on the images endpoints
Storage and bandwidthYours, plus the source'sYoursThe browser loads from img.amodb.dev
CacheYoursYours30 days at the edge

Pricing

Image downloads carry no key. Your plan limits only the JSON calls that give you file names, and list replies already include poster_path for every item, so one list call feeds a whole grid. Choose 20, 40 or 120 requests per minute on pricing. Access is prepaid for 1, 3, 6, 12 or 24 months with 0, 10, 20, 30 and 40 percent off, nothing renews by itself, and there is no free tier.