TMDB API

Our Goal

Our goal is to learn how to interact with The Movie Database (TMDB) API. TMDB's API is used by thousands of applications today and provides programmatic access to a vast database of movies and TV shows. Through this lesson, we'll learn how to fetch movie information and rate them.

Reading Documentation

The first step of working with any API is collecting a few pieces of information:

  • What protocol, host and path (basically, what URL) will provide access to the appropriate resource?
  • What parameters do I need to include in the request?
  • Is authentication required?

In order to answer these questions, we'll start by browsing the official TMDB API documentation. The documentation provides comprehensive information about each endpoint, including the location of resources and requirements for interacting with them.

Scrolling to the left column we'll find Movies section with "Details" page. Clicking this will display documentation for it:


First API Request

Putting everything together, we now know the following about the request we need to make:

  • The complete URL is https://api.themoviedb.org/3/movie/{movie_id}
  • The resource path includes the {movie_id} parameter, which we need to replace with the specific movie ID. In this case, the movie ID is 27205, which corresponds to the movie "Inception".
  • We will need to make an authenticated request by including our Access Token in the Authorization header.

TMDB requires all API access be done over HTTPS to improve the security of its system. This is why the scheme begins with https and not http.

Let's try to get details for "Inception" (movie ID: 27205) without specifying an API key:

$ http GET https://api.themoviedb.org/3/movie/27205
HTTP/1.1 401 Unauthorized
Alt-Svc: h3=":443"; ma=86400
Cache-Control: public, max-age=300
Connection: keep-alive
Content-Type: application/json; charset=utf-8
Date: Mon, 25 Nov 2024 18:11:11 GMT
Server: openresty
Transfer-Encoding: chunked
Vary: Origin
Via: 1.1 40bdf73541ecf41ea1aa6f68489f3e2e.cloudfront.net (CloudFront)
X-Amz-Cf-Id: xyv_3tLoYORixEC9CTGNdftsF3ni7HwYdFuuuRouroJGaLHR1xzC7Q==
X-Amz-Cf-Pop: BUD50-P2
X-Cache: Error from cloudfront
{
    "status_code": 7,
    "status_message": "Invalid API key: You must be granted a valid key.",
    "success": false
}

As expected, we get an error telling us we need proper authentication to access any data. TMDB requires an Access Token for authentication.

Gaining Access

Now that we've established we need authentication, let's set up our credentials. This requires a few steps:

For the purpose of this exercise you can simply enter http://localhost:3000 as the URL as we won't be using it.

Before we can make any requests, we need to set up authentication:

  1. Go to TMDB website and create a free account
  2. Go to your Profile Settings
  3. Click "API" in the left sidebar
  4. Follow the steps to request an API key:
    • Select "Developer" option
    • Accept the terms of use
    • Fill in the basic information
  5. After approval, you'll receive an API Read Access Token.

The Read Access Token allows us to make authenticated requests without implementing a full OAuth flow. This makes it perfect for learning API operations.

Fetching Movie Information

Now that we have our API access token, let's try to get information about "Inception" again:

http GET https://api.themoviedb.org/3/movie/27205 \
    "Authorization: Bearer YOUR_ACCESS_TOKEN"
HTTP/1.1 200 OK
Alt-Svc: h3=":443"; ma=86400
Cache-Control: public, max-age=2884
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:33:26 GMT
ETag: W/"9dfd35b0a91ac947a4e2c12a7c040c42"
Server: openresty
Transfer-Encoding: chunked
Vary: Accept-Encoding,accept-encoding, Origin
Via: 1.1 92f3d3fb9fe3ca5166aa49ba6fcab7b6.cloudfront.net (CloudFront)
X-Amz-Cf-Id: AIhCk-S3hNHlRnH0AzQdmf1_8GazFPCRHaCJ4_7bYLmvAatQW09Pag==
X-Amz-Cf-Pop: MXP53-P4
X-Cache: Miss from cloudfront
x-memc: HIT
x-memc-age: 24625
x-memc-expires: 2884
x-memc-key: ba0bb4b9033cab23506074980327c2f8
{
    "adult": false,
    "backdrop_path": "/8ZTVqvKDQ8emSGUEMjsS4yHAwrp.jpg",
    "belongs_to_collection": null,
    "budget": 160000000,
    "genres": [
        {
            "id": 28,
            "name": "Action"
        },
        {
            "id": 878,
            "name": "Science Fiction"
        },
        {
            "id": 12,
            "name": "Adventure"
        }
    ],
    "homepage": "https://www.warnerbros.com/movies/inception",
    "id": 27205,
    "imdb_id": "tt1375666",
    "origin_country": [
        "US",
        "GB"
    ],
    "original_language": "en",
    "original_title": "Inception",
    "overview": "Cobb, a skilled thief who commits corporate espionage by infiltrating the subconscious of his targets is offered a chance to regain his old life as payment for a task considered to be impossible: \"inception\", the implantation of another person's idea into a target's subconscious.",
    "popularity": 116.011,
    "poster_path": "/ljsZTbVsrQSqZgWeep2B1QiDKuh.jpg",
    "production_companies": [
        {
            "id": 923,
            "logo_path": "/8M99Dkt23MjQMTTWukq4m5XsEuo.png",
            "name": "Legendary Pictures",
            "origin_country": "US"
        },
        {
            "id": 9996,
            "logo_path": "/3tvBqYsBhxWeHlu62SIJ1el93O7.png",
            "name": "Syncopy",
            "origin_country": "GB"
        },
        {
            "id": 174,
            "logo_path": "/zhD3hhtKB5qyv7ZeL4uLpNxgMVU.png",
            "name": "Warner Bros. Pictures",
            "origin_country": "US"
        }
    ],
    "production_countries": [
        {
            "iso_3166_1": "GB",
            "name": "United Kingdom"
        },
        {
            "iso_3166_1": "US",
            "name": "United States of America"
        }
    ],
    "release_date": "2010-07-15",
    "revenue": 825532764,
    "runtime": 148,
    "spoken_languages": [
        {
            "english_name": "English",
            "iso_639_1": "en",
            "name": "English"
        },
        {
            "english_name": "French",
            "iso_639_1": "fr",
            "name": "Français"
        },
        {
            "english_name": "Japanese",
            "iso_639_1": "ja",
            "name": "日本語"
        },
        {
            "english_name": "Swahili",
            "iso_639_1": "sw",
            "name": "Kiswahili"
        }
    ],
    "status": "Released",
    "tagline": "Your mind is the scene of the crime.",
    "title": "Inception",
    "video": false,
    "vote_average": 8.369,
    "vote_count": 36565
}

The response JSON contains much more information than what you might typically see on TMDB's website. While some fields like title and release_date are self-explanatory, others require some explanation. For instance, backdrop_path is a partial URL that needs to be combined with TMDB's base image URL to get the full movie backdrop image. The budget and revenue fields are in US dollars, and popularity is a floating-point number that indicates the current trending status of the movie.

Note that the genres field contains an array of objects, each with its own id and name. This is a common pattern in APIs where related data (in this case, movie genres) is embedded within the main resource. TMDB has detailed documentation about these genre IDs and their meanings.

Let's see what happens when we request a movie that doesn't exist:

$ http GET https://api.themoviedb.org/3/movie/999999999 \
    "Authorization: Bearer YOUR_ACCESS_TOKEN"
HTTP/1.1 404 Not Found
Alt-Svc: h3=":443"; ma=86400
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:34:31 GMT
Server: openresty
Transfer-Encoding: chunked
Vary: Accept-Encoding,accept-encoding, Origin
Via: 1.1 5d05f2fc24319f66178c71a89f65afb4.cloudfront.net (CloudFront)
X-Amz-Cf-Id: rLB6M2TWJqmf4oQTHHXGEeONIqrtOC6-mxJXGhFQbnGLE08ngQqIAg==
X-Amz-Cf-Pop: MXP53-P4
X-Cache: Error from cloudfront
{
    "status_code": 34,
    "status_message": "The resource you requested could not be found.",
    "success": false
}

A 404 Not Found response makes sense as this movie ID doesn't exist in their database.

Rating A Movie

Now that we can fetch movie information, let's try rating a movie. According to the documentation, we need:

  • A URL of https://api.themoviedb.org/3/movie/{movie_id}/rating
  • A value parameter between 0.5 and 10.0, representing our rating
  • The HTTP method POST since we're creating a new rating
  • Our access token in the Authorization header

Let's try to rate "Inception":

$ http POST https://api.themoviedb.org/3/movie/27205/rating \
    "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    value:=9
HTTP/1.1 201 Created
Alt-Svc: h3=":443"; ma=86400
Connection: keep-alive
Content-Length: 94
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:35:08 GMT
Server: openresty
Via: 1.1 f0d757c9b2cb754e4844ae59614d9100.cloudfront.net (CloudFront)
X-Amz-Cf-Id: smAtKtrmER6WJRzAhWarhRMuG5hLTrAgNbRZWgKjuoDoWwjnKKfKnA==
X-Amz-Cf-Pop: SOF50-P2
X-Cache: Miss from cloudfront
cache-control: public, max-age=0
etag: W/"04302547257ccb7e68844cdd59055002"
vary: accept-encoding, Origin
{
    "status_code": 1,
    "status_message": "Success.",
    "success": true
}

The response confirms our rating was recorded successfully. It's worth noting that while the request seems simple, TMDB is doing several things behind the scenes:

  • Validating that the movie exists
  • Checking if we've already rated this movie (in which case it updates the existing rating)
  • Recalculating the movie's average rating
  • Recording this rating in our account history

If we wanted to submit an updated rating for the same movie we would send a new POST request with updated value:

$ http POST https://api.themoviedb.org/3/movie/27205/rating \
    "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    value:=10
HTTP/1.1 201 Created
Alt-Svc: h3=":443"; ma=86400
Connection: keep-alive
Content-Length: 94
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:35:53 GMT
Server: openresty
Via: 1.1 3189d5a79867614f76bbd8fdfe668c5c.cloudfront.net (CloudFront)
X-Amz-Cf-Id: z3qcDcgdZHsgZgvxN5X_hSfaNikIE6Jzxiqzr18-pfHwh_Tn8v_zKQ==
X-Amz-Cf-Pop: SOF50-P2
X-Cache: Miss from cloudfront
cache-control: public, max-age=0
etag: W/"04302547257ccb7e68844cdd59055002"
vary: accept-encoding, Origin
{
    "status_code": 1,
    "status_message": "Success.",
    "success": true
}

If we try to submit an invalid rating:

$ http POST https://api.themoviedb.org/3/movie/27205/rating \
    "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    value:=11.0
HTTP/1.1 400 Bad Request
Alt-Svc: h3=":443"; ma=86400
Connection: keep-alive
Content-Length: 112
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:36:56 GMT
Server: openresty
Via: 1.1 ae0b3d83f44efc7ed7498f0d5e1a0dd2.cloudfront.net (CloudFront)
X-Amz-Cf-Id: jpQKZV0fT9o2Zp9eZS8knckLloPo1EjRkAv3IFuasosg2ikVkV0MwQ==
X-Amz-Cf-Pop: SOF50-P2
X-Cache: Error from cloudfront
cache-control: public, max-age=0
vary: accept-encoding, Origin
{
    "status_code": 18,
    "status_message": "Value too high: Value must be less than, or equal to 10.0.",
    "success": false
}

Deleting A Rating

To delete our rating, we use the DELETE HTTP method on the same endpoint:

$ http DELETE https://api.themoviedb.org/3/movie/27205/rating \
    "Authorization: Bearer YOUR_ACCESS_TOKEN"
HTTP/1.1 200 OK
Alt-Svc: h3=":443"; ma=86400
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json;charset=utf-8
Date: Mon, 25 Nov 2024 18:37:21 GMT
Server: openresty
Transfer-Encoding: chunked
Vary: Accept-Encoding, accept-encoding, Origin
Via: 1.1 956fe4e84d87237dd08155132ba1c0a2.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 2DMkU1CC0fgHU0XpAPwxteUOgS_BmsWEJxOZWikRP5Wcyn6n8YAyKA==
X-Amz-Cf-Pop: SOF50-P2
X-Cache: Miss from cloudfront
cache-control: public, max-age=0
etag: W/"0566f98871ddfd7f6ade28aaefb5167d"
{
    "status_code": 13,
    "status_message": "The item/record was deleted successfully.",
    "success": true
}

TMDB API and REST Principles Analysis

Let's look at how TMDB's API design holds up against REST principles and common API practices.

Resource Design

The TMDB API does resource design well. They use straightforward paths like /movie/{id} and /movie/{id}/rating that clearly show what you're working with and how different resources relate to each other. Instead of using URLs with verbs (like/getMovie or /createRating), they stick to identifying resources - which is exactly what REST suggests.

Here's a comparison of some common operations:

Objective HTTP Method Path
Get movie details GET /movie/{id}
Rate a movie POST /movie/{id}/rating
Delete a rating DELETE /movie/{id}/rating

This makes sense - you use GET method to retrieve data, POST to create something new, and DELETE to remove things. It's consistent and easy to understand.

HTTP Methods

One issue is how they handle updates. When you want to change a movie rating, you use POST - the same method you'd use to create a new rating. In REST, we'd typically use PUT or PATCH for updates.

Their approach does make things simpler for developers since you don't need to check if a rating exists before deciding whether to use POST or PUT. But it's not exactly RESTful. This kind of trade-off is pretty common in real APIs, where making things easier to use sometimes wins over following REST principles perfectly.

Error Handling Approach

The way TMDB handles status codes is unconventional but serves a specific purpose. Look at what they return:

{
    "status_code": 7,
    "status_message": "Invalid API key: You must be granted a valid key.",
    "success": false
}

TMDB uses custom numeric codes to provide more detailed error information within HTTP status code categories. For example, different types of authentication failures might all return HTTP 401, but TMDB's custom codes (like 7) let developers handle specific cases with more granularity. You can find the list of errors here.

However, some aspects could still be improved:

  • The numeric codes aren't self-documenting - you have to look up what "7" means
  • The success field doesn't add value - we already know if it succeeded from the HTTP status code

A clearer approach, in our opinion, while maintaining the same granularity would be:

HTTP/1.1 401 Unauthorized
{
    "code": "INVALID_API_KEY",
    "message": "Invalid API key: You must be granted a valid key",
}

This would give you the same granular error handling but with more immediately understandable error types.

Real-World Impact

We can't know for certain why TMDB made this design decision, but using POST instead of PUT was likely due to the simpler usage for clients, as they don't need to first check if a rating exists before deciding whether to use POST or PUT. Another question you might have is, if this is not following the best practices, why don't they just update their API?

Modifying public APIs has to be done very thoughtfully, and usually removing resources or parameters will require releasing a new version of the API. This allows consumers to opt-in to the API changes and goes a long way to preventing breaking changes from disrupting existing API clients.

In addition, while these adjustments might make the API a bit more conventional, it would otherwise not change the core functionality of the API. It's hard to make a strong case for potentially disruptive changes that will involve a lot of work for engineers (both inside and outside of TMDB) when there isn't a clear functional benefit resulting from the efforts.

What We Can Learn

The TMDB API shows us that even widely-used APIs aren't perfect. Some parts, like their resource design, are done well. Others, like their status codes, could be better. This gives us several important lessons:

  • APIs in the real world often deviate from textbook patterns and best practices
  • Always read the documentation carefully - don't assume you know how things work just because you've used similar APIs
  • Pay attention to example responses in documentation to understand what data you'll actually get back
  • Even if an API isn't perfect, it can still be very useful and reliable

The most important takeaway is that successful integration with any API comes down to understanding its specific behavior through documentation and testing, rather than relying on assumptions or theoretical principles.

Exercises

  1. Use the TMDB search API to find the IDs for three movies of your choice.

  2. Look up the watchlist endpoints in the TMDB documentation. Add the three movies from Exercise 1 to your watchlist.