{"openapi":"3.0.3","info":{"version":"2.0.0","title":"Movie information API","description":"This API provides access to a catalogue of movies, the people who made them, and ratings from several sources, together with JWT-based user accounts.\n\n**Note:** the original IMDB-derived dataset was hosted on PlanetScale, whose free tier shut down in April 2024. The API now ships with a small curated sample catalogue in SQLite so it runs with no external services. Titles, years and credits are real; ratings and box-office figures are approximate."},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Enter the bearer token returned by /user/login. Failed authentication returns 401 with a WWW-Authenticate header carrying the machine-readable reason."}},"schemas":{"InvalidYearFormat":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Invalid year format. Format must be yyyy."}}},"MissingBearer":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Authentication required"}}},"MissingAuthHeader":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Authentication required"}}},"TokenExpired":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Your session has expired"}}},"InvalidJWT":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Authentication failed"}}},"InvalidPageFormat":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Invalid page format. page must be a number."}}},"TooManyRequests":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"Too many attempts from this address. Please try again later."}}},"ValidationError":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"Password must be at least 8 characters"}}},"Forbidden":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"Forbidden"}}},"NotFound":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"No record exists of a movie with this ID"}}},"ServerError":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"Internal server error"}}}}},"paths":{"/movies/search":{"get":{"tags":["Movies"],"description":"Search the catalogue. Results are paginated; `per_page` is capped at 100.","parameters":[{"name":"title","in":"query","description":"Text to search for in the primary title of the movie.","required":false,"schema":{"type":"string"}},{"name":"year","in":"query","description":"Year of initial release, as four digits.","required":false,"schema":{"type":"string","pattern":"^\\d{4}$"}},{"name":"genre","in":"query","description":"Restrict results to films tagged with this genre, e.g. `Drama`.","required":false,"schema":{"type":"string"}},{"name":"minRating","in":"query","description":"Only return films with an IMDB rating at or above this value.","required":false,"schema":{"type":"number","minimum":0,"maximum":10}},{"name":"sort","in":"query","description":"Result ordering.","required":false,"schema":{"type":"string","enum":["relevance","rating","newest","oldest"],"default":"relevance"}},{"name":"page","in":"query","description":"Page number, starting at 1.","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"per_page","in":"query","description":"Results per page. Values above 100 are clamped to 100.","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"An array of objects containing title, year, imdbID, imdbRating, rottenTomatoesRating, metacriticRating and classification properties. The results are limited to 100 per page. An example of one object in the array is shown below.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","example":"Star Trek: First Contact"},"year":{"type":"integer","example":1996},"imdbID":{"type":"string","example":"tt0117731"},"imdbRating":{"type":"number","example":7.6},"rottenTomatoesRating":{"type":"number","example":92},"metacriticRating":{"type":"number","example":71},"classification":{"type":"string","example":"PG-13"}}}},"pagination":{"type":"object","properties":{"total":{"type":"number","example":6},"lastPage":{"type":"number","example":1},"perPage":{"type":"number","example":100},"currentPage":{"type":"number","example":1},"from":{"type":"number","example":0},"to":{"type":"number","example":6}}}}}}}},"400":{"description":"Invalid year query parameter. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/InvalidYearFormat"},{"$ref":"#/components/schemas/InvalidPageFormat"}]}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TooManyRequests"}}}}}}},"/movies/data/{imdbID}":{"get":{"tags":["Movies"],"description":"Get data for a movie by imdbID","operationId":"getMovieData","parameters":[{"name":"imdbID","in":"path","description":"The imdbID of the movie","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"An object containing the data for the movie.","content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","example":"Star Trek: First Contact"},"year":{"type":"integer","example":1996},"runtime":{"type":"integer","example":111},"genres":{"type":"array","items":{"type":"string","example":"Action"},"example":["Action","Adventure","Drama"]},"country":{"type":"string","example":"United States"},"principals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"nm0001772"},"name":{"type":"string","example":"Patrick Stewart"},"category":{"type":"string","example":"actor"},"characters":{"type":"array","items":{"type":"string","example":"Picard"}}},"required":["id","name","category","characters"]},"example":[{"id":"nm0005772","category":"cinematographer","name":"Matthew F. Leonetti","characters":[]},{"id":"nm0001772","category":"actor","name":"Patrick Stewart","characters":["Picard"]},{"id":"nm0000408","category":"actor","name":"Jonathan Frakes","characters":["Riker"]},{"id":"nm0000653","category":"actor","name":"Brent Spiner","characters":["Data"]},{"id":"nm0000996","category":"actor","name":"LeVar Burton","characters":["Geordi"]},{"id":"nm0734472","category":"writer","name":"Gene Roddenberry","characters":[]},{"id":"nm0075834","category":"writer","name":"Rick Berman","characters":[]},{"id":"nm0103804","category":"writer","name":"Brannon Braga","characters":[]},{"id":"nm0601822","category":"writer","name":"Ronald D. Moore","characters":[]},{"id":"nm0000025","category":"composer","name":"Jerry Goldsmith","characters":[]}]},"ratings":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","example":"Internet Movie Database"},"value":{"type":"number","example":7.6}}}},"boxoffice":{"type":"integer","example":92027888},"poster":{"type":"string","example":"https://m.media-amazon.com/images/M/MV5BYzMzZmE3MTItODYzYy00YWI5LWFkNWMtZTY5NmU2MDkxYWI1XkEyXkFqcGdeQXVyMjUzOTY1NTc@._V1_SX300.jpg"},"plot":{"type":"string","example":"The Borg travel back in time intent on preventing Earth's first contact with an alien species. Captain Picard and his crew pursue them to ensure that Zefram Cochrane makes his maiden flight reaching warp speed."}}}}}},"400":{"description":"Invalid query parameters. Click on 'Schema' to see possible error responses.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Query parameters are not permitted."}}}]}}}},"404":{"description":"Unknown film","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotFound"}}}}}}},"/people/{id}":{"get":{"tags":["People"],"description":"Get information about a person (actor, writer, director etc.) from their IMDB ID.","parameters":[{"name":"id","in":"path","description":"The person's IMDB ID","required":true,"schema":{"type":"string"}}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"An object containing data about that person","content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","example":"Patrick Stewart"},"birthYear":{"type":"integer","example":1940},"deathYear":{"type":"integer","example":null},"roles":{"type":"array","items":{"type":"object","properties":{"movieName":{"type":"string","example":"Star Trek: First Contact"},"movieId":{"type":"string","example":"tt0117731"},"category":{"type":"string","example":"actor"},"characters":{"type":"array","items":{"type":"string","example":"Picard"}},"imdbRating":{"type":"number","example":7.6}}}}}}}}},"400":{"description":"Invalid query parameters. Click on 'Schema' to see possible error responses.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Query parameters are not permitted."}}}]}}}},"401":{"description":"Unauthorized. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MissingAuthHeader"},{"$ref":"#/components/schemas/TokenExpired"},{"$ref":"#/components/schemas/InvalidJWT"}]}}}},"404":{"description":"The requested person could not be found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"No record exists of a person with this ID"}}}}}}}}},"/user/register":{"post":{"tags":["Authentication"],"description":"Create an account. Passwords must be at least 8 characters.","requestBody":{"description":"An object containing the email and password of the user to be registered.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"mike@gmail.com"},"password":{"type":"string","example":"password"}}}}}},"responses":{"201":{"description":"User successfully created","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","example":"User created"}}}}}},"400":{"description":"Missing or invalid email/password","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"409":{"description":"User already exists","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"User already exists"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TooManyRequests"}}}}}}},"/user/login":{"post":{"description":"Exchange credentials for a bearer token (10 minutes) and a refresh token (24 hours, or 7 days with `longExpiry`). Token lifetimes are fixed by the server.","tags":["Authentication"],"requestBody":{"description":"The credentials of the user to log in.","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"mike@gmail.com"},"password":{"type":"string","example":"password"},"longExpiry":{"type":"boolean","example":false,"description":"Request a 7-day refresh token instead of the default 24 hours."}}}}}},"responses":{"200":{"description":"Log in successful","content":{"application/json":{"schema":{"type":"object","properties":{"bearerToken":{"type":"object","properties":{"token":{"type":"string","example":"ajsonwebtoken"},"token_type":{"type":"string","example":"Bearer"},"expires_in":{"type":"number","example":600}}},"refreshToken":{"type":"object","properties":{"token":{"type":"string","example":"ajsonwebtoken"},"token_type":{"type":"string","example":"Refresh"},"expires_in":{"type":"number","example":86400}}}}}}}},"400":{"description":"Invalid log in request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body incomplete, both email and password are required"}}}}}},"401":{"description":"Log in failed","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Incorrect email or password"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TooManyRequests"}}}}}}},"/user/refresh":{"post":{"description":"Exchange a refresh token for a new bearer token and a **new** refresh token. The submitted token is retired immediately. Presenting a retired token revokes every token issued from the same login.","tags":["Authentication"],"requestBody":{"description":"The refresh token","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","example":"ajsonwebtoken"}}}}}},"responses":{"200":{"description":"Token successfully refreshed","content":{"application/json":{"schema":{"type":"object","properties":{"bearerToken":{"type":"object","properties":{"token":{"type":"string","example":"ajsonwebtoken"},"token_type":{"type":"string","example":"Bearer"},"expires_in":{"type":"number","example":600}}},"refreshToken":{"type":"object","properties":{"token":{"type":"string","example":"ajsonwebtoken"},"token_type":{"type":"string","example":"Refresh"},"expires_in":{"type":"number","example":86400}}}}}}}},"400":{"description":"Invalid refresh request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body incomplete, refresh token required"}}}}}},"401":{"description":"Unauthorized. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExpired"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TooManyRequests"}}}}}}},"/user/logout":{"post":{"description":"Log the user out, invalidating the refresh token","tags":["Authentication"],"requestBody":{"description":"The refresh token","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","example":"ajsonwebtoken"}}}}}},"responses":{"200":{"description":"Token successfully invalidated","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":false},"message":{"type":"string","example":"Token successfully invalidated"}}}}}},"400":{"description":"Invalid refresh request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body incomplete, refresh token required"}}}}}},"401":{"description":"Unauthorized. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExpired"}}}}}}},"/user/{email}/profile":{"get":{"description":"Provide profile information","tags":["Authentication"],"parameters":[{"name":"email","in":"path","description":"The profile's email","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"An object containing the profile","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"cyrus@gmail.com"},"firstName":{"type":"string","example":"Cyrus"},"lastName":{"type":"string","example":"Nguyen"},"dob":{"type":"string","example":"2000-01-01"},"address":{"type":"string","example":"Brisbane, Australia"}}}}}},"400":{"description":"Invalid refresh request","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body incomplete, refresh token required"}}}}}},"401":{"description":"Unauthorized. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExpired"}}}},"404":{"description":"Profile not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"User not found"}}}}}},"200 Not Owner":{"description":"An object containing the profile without dob and address","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"cyrus@gmail.com"},"firstName":{"type":"string","example":"Cyrus"},"lastName":{"type":"string","example":"Nguyen"}}}}}}}},"put":{"description":"Update profile information","tags":["Authentication"],"requestBody":{"description":"An object containing the first name (firstName), last name (lastName), date of birth (dob) and address (address).","required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"type":"string","example":"Cyrus"},"lastName":{"type":"string","example":"Nguyen"},"dob":{"type":"string","example":"2000-01-01"},"address":{"type":"string","example":"Brisbane, Australia"}}}}}},"parameters":[{"name":"email","in":"path","description":"The profile's email","required":true,"schema":{"type":"string"}}],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"An object containing the updated profile","content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","example":"cyrus@gmail.com"},"firstName":{"type":"string","example":"Cyrus"},"lastName":{"type":"string","example":"Nguyen"},"dob":{"type":"string","example":"2000-01-01"},"address":{"type":"string","example":"Brisbane, Australia"}}}}}},"400":{"description":"Object does not contain all of the fields","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body incomplete: firstName, lastName, dob and address are required."}}}}}},"401":{"description":"Unauthorized. Click on 'Schema' below to see the possible error responses.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenExpired"}}}},"403":{"description":"Authenticated as a different user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Forbidden"}}}},"404":{"description":"Profile not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":true},"message":{"type":"string","example":"User not found"}}}}}},"400 Not String":{"description":"Any of the fields are not strings","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Request body invalid: firstName, lastName and address must be strings only."}}}}}},"400 Wrong Date Format":{"description":"If the date of birth is not a valid YYYY-MM-DD date (e.g. no April 31 or February 30, or February 29 on a non-leap year) or the date is not in the past","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"boolean","example":"true"},"message":{"type":"string","example":"Invalid input: dob must be a real date in format YYYY-MM-DD."}}}}}}}}},"/health":{"get":{"tags":["Administration"],"summary":"Liveness and database connectivity check","responses":{"200":{"description":"API and database are reachable","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"database":{"type":"string","example":"up"}}}}}},"503":{"description":"Database unreachable"}}}}},"servers":[{"url":"https://movie-api-cyrus.fly.dev","description":"This server"}]}