PHP client for the amodb movie and TV metadata API.
PHP client for the amodb API. amodb serves movie and TV metadata through a TMDB v3 compatible API and an OMDb compatible endpoint.
Existing TMDB clients work by changing the base URL to https://api.amodb.dev/3. This package is a ready made client for the same API.
amodb is a paid API. Get a key at https://amodb.dev/pricing.
composer require amodb/amodb-phpIt needs PHP 8.1 or newer with the json and curl extensions.
<?php
require __DIR__ . '/vendor/autoload.php';
use Amodb\Client;
$amodb = new Client((string) getenv('AMODB_API_KEY'));
$hits = $amodb->search->movie('fight club');
$movie = $amodb->movies->details($hits['results'][0]['id']);
echo $movie['title'], ' ', substr($movie['release_date'], 0, 4), "\n";
echo $amodb->imageUrl($movie['poster_path'], 'w342'), "\n";Every method returns the decoded JSON body as a PHP array. The fields are the same as in TMDB v3. The amodb extension methods document their reply shape in the @return tag, so editors and static analysers can check the keys.
By default the key is sent as Authorization: Bearer KEY. To send it as the api_key query parameter, use the auth option:
$amodb = new Client($key, ['auth' => 'query']);Keys are 32 lowercase hex characters. Keep them in environment variables, never in source control. An empty key raises InvalidArgumentException when you create the client. The client hides the key from var_dump and print_r. The curl transport never puts the request URL in an error message.
$movies = $amodb->search->movie('alien', ['year' => 1979, 'page' => 1]);
$shows = $amodb->search->tv('dark');
$people = $amodb->search->person('sigourney weaver');
$mixed = $amodb->search->multi('matrix');collection, company and keyword work the same way. The second argument is an array of extra query parameters.
$movie = $amodb->movies->details(550, ['credits', 'images', 'ratings']);
echo count($movie['credits']['cast']), " cast members\n";The array is joined with commas into append_to_response=credits,images,ratings. A plain string works too. The API takes up to 20 names and ignores unknown names without an error.
- Movies:
credits,images,videos,keywords,translations,external_ids,similar,recommendations,reviews,release_dates,watch/providers,alternative_titles,lists,moods,soundtracks,ratings,wikidata,showtimes. - TV shows: the same names, with
content_ratingsandepisode_groupsin place ofrelease_dates. - People:
combined_credits,movie_credits,tv_credits,external_ids,images.
The appended moods, ratings and soundtracks sections have no id key. wikidata and showtimes keep theirs. To get the venues of one country with showtimes, pass the country in $params:
$movie = $amodb->movies->details(550, ['showtimes'], ['country' => 'RU']);More calls:
$amodb->movies->credits(550);
$amodb->movies->recommendations(550, page: 2);
$amodb->tv->season(1396, 1);
$amodb->tv->episode(1396, 1, 1);
$amodb->people->combinedCredits(287);
$amodb->discover->movie(['with_genres' => [28, 12], 'sort_by' => 'popularity.desc']);
$amodb->trending->get('movie', 'week');
$amodb->genres->movies();
$amodb->movies->latest();
$amodb->tv->episodeGroups(1396);List methods take $page from 1 to 500. movies->lists() and tv->lists() are paged too.
Resources on the client: movies, tv, people, search, discover, trending, genres, collections, companies, networks, keywords, certifications, watchProviders and omdb. Every method takes an optional $params array and an optional $language.
// Default for every call
$amodb = new Client($key, ['language' => 'ru-RU']);
// One call
$movie = $amodb->movies->details(550, language: 'fr-FR');The API serves 14 base languages: en ru uk be zh pt bg he cs ro fr pl it es. Any other tag falls back to en.
$url = $amodb->imageUrl($movie['poster_path'], 'w500');
// https://img.amodb.dev/t/p/w500/abc123.jpgSizes are w92, w154, w185, w300, w342, w500, w780, w1280, h632 and original. imageUrl returns null for a null or empty path. Image URLs need no key.
$film = $amodb->omdb->byId('tt0137523', plot: 'full');
$film = $amodb->omdb->byTitle('Fight Club', year: 1999);
$page = $amodb->omdb->search('fight', type: 'movie', page: 2);
echo $film['Title'], ' ', $film['imdbRating'], "\n";Bodies keep the OMDb field names. The key is sent as apikey. A body with "Response": "False" raises AmodbException with status 200 and the OMDb Error text as the message.
$found = $amodb->find('tt0137523', 'imdb_id');
echo $found['movie_results'][0]['title'], "\n"; // Fight Club
$found = $amodb->find('361', 'kinopoisk_id');
$found = $amodb->find('Q35332', 'wikidata_id');
echo $found['person_results'][0]['name'], "\n"; // Brad PittThese sources return results: imdb_id (tt... for titles, nm... for people), tvdb_id, facebook_id, twitter_id, instagram_id, wikidata_id and kinopoisk_id. freebase_mid, freebase_id, tvrage_id, tiktok_id and youtube_id are accepted and return empty lists. A missing or unknown source answers HTTP 400 with status_code 22.
The reply has movie_results, person_results, tv_results, tv_episode_results and tv_season_results. The last two are always empty. Every item has a media_type.
Methods for the amodb extension endpoints exist on both movies and tv.
$moods = $amodb->movies->moods(155);
echo $moods['source'], "\n"; // measured
echo $moods['results'][0]['name'], ' ', $moods['results'][0]['avg'], "\n"; // action 3.9source is measured for viewer data, estimated when the meters come from genres (whole numbers), or null with an empty results list when the title has no data. Each result has name, avg and values (up to 24 readings) on a 0 to 10 scale. The names are action, fear, humor, pace, profanity, romance, sadness, sex and violence. A title may have fewer than nine.
$albums = $amodb->movies->soundtracks(155)['results'];
echo $albums[0]['album'], ' ', $albums[0]['track_count'], " tracks\n"; // The Dark Knight 14 tracks
echo $albums[0]['tracks'][0]['title'], "\n"; // Why So Serious?
results is empty when no album is known. An album has source (musicbrainz or deezer), album, artist (can be null), released, url, track_count and tracks. A track has disc, number, title, artist and duration_ms.
$ratings = $amodb->movies->ratings(550);
echo $ratings['imdb']['value'], ' from ', $ratings['imdb']['votes'], " votes\n"; // 8.8 from 2669989 votesThe providers are imdb, kinopoisk, metacritic, rotten_tomatoes and tmdb. Each is {value, votes}, or null when the score is missing. votes can be null. imdb, kinopoisk and tmdb are out of 10, metacritic and rotten_tomatoes out of 100.
$wikidata = $amodb->movies->wikidata(604);
echo $wikidata['budget']['value'], ' ', $wikidata['budget']['currency'], "\n"; // 150000000 USD
echo $wikidata['follows']['label'], ' ', $wikidata['follows']['tmdb_id'], "\n"; // The Matrix 603budget and box_office are {value, currency} or null (box_office adds worldwide). follows and followed_by are links or null. sitelinks maps a language code to a Wikipedia page title. awards, nominations, based_on, filming_locations, narrative_locations and original_broadcasters are lists. A link has wikidata_id and label, plus tmdb_id and media_type when the item is a title in the catalog.
// Which countries have showtimes
$countries = $amodb->movies->showtimes(550);
echo $countries['countries'][0]['country'], ' ', $countries['countries'][0]['venues'], " venues\n";
// Venues and showtimes for one country
$venues = $amodb->movies->showtimes(550, 'RU');
echo $venues['results'][0]['name'], ' ', $venues['results'][0]['showtimes'][0]['start'], "\n";The country code is optional. Without it, results is empty and countries holds the counts per country: venues, cities, showtimes, first_day and last_day. With it, results lists the venues, and each venue has a showtimes list. start is the local time at the venue. Only days from today on are listed.
These TMDB paths answer 404 with status_code 34, so the client has no method for them: aggregate_credits, changes, person/{id}/translations, person/{id}/tagged_images, credit/{id}, review/{id}, tv/episode_group/{id}, images, translations and alternative names of collections, companies and networks, and configuration/timezones. Everything under account, authentication, guest_session and list answers 405 with status_code 37, and so does any rating or account_states path.
Some served calls return little: reviews is empty for most titles, and season and episode videos and translations are empty. The include_adult parameter is ignored, because adult titles and people are never served.
$config = $amodb->configuration();
$plans = $amodb->plans();
$data = $amodb->request('/movie/550/credits', ['language' => 'en-US']);request() is the escape hatch for any path under the base URL. It uses the same auth, language, retries and error handling.
use Amodb\Exception\AmodbException;
use Amodb\Exception\NetworkException;
use Amodb\Exception\RateLimitException;
use Amodb\Exception\TimeoutException;
try {
$movie = $amodb->movies->details(550);
} catch (RateLimitException $e) {
echo 'Rate limited, retry in ', $e->getRetryAfter() ?? 'a few', " seconds\n";
} catch (TimeoutException | NetworkException $e) {
echo 'No response: ', $e->getMessage(), "\n";
} catch (AmodbException $e) {
echo $e->getStatus(), ' ', $e->getStatusCode(), ' ', $e->getStatusMessage(), "\n";
}| Class | Raised when |
|---|---|
AmodbException |
The API answers with an error status, the body is not JSON, or OMDb answers Response: False. Base class of the others. |
RateLimitException |
HTTP 429 after the last retry. |
TimeoutException |
No response within the timeout. |
NetworkException |
The connection failed before a response arrived. |
| Method | Returns |
|---|---|
getStatus() |
HTTP status, or 0 when no response arrived. |
getStatusCode() |
The status_code of the error body, or null. |
getStatusMessage() |
The status_message, or a short text such as HTTP 502. |
getBody() |
The parsed JSON body, or the raw text when it was not JSON. |
getRetryAfter() |
On RateLimitException: seconds from Retry-After, or null. |
Exception::getCode() is final in PHP, so the TMDB code is in getStatusCode().
API errors use the TMDB body shape:
| HTTP | status_code | Meaning |
|---|---|---|
| 401 | 7 | Invalid API key |
| 401 | 10 | Suspended API key |
| 402 | 36 | Plan expired |
| 404 | 34 | Resource not found |
| 405 | 37 | Method not offered by amodb |
| 429 | 25 | Over the rate limit |
| 500 | 11 | Internal error |
| 504 | 24 | Backend timed out |
- Only HTTP 429 is retried. Timeouts and network errors are raised at once.
- The client waits for
Retry-After(seconds or an HTTP date, clamped to 0 to 60 seconds). Without the header it waits 1, 2, then 4 seconds. - It retries at most 3 times, so a request is sent at most 4 times. Then it raises
RateLimitException. timeoutis 15 seconds per request by default.
$amodb = new Client($key, ['timeout' => 5, 'maxRetries' => 1]);Pass an array as the second argument, or an Amodb\Options object.
| Option | Default | Meaning |
|---|---|---|
baseUrl |
https://api.amodb.dev/3 |
Base URL of the TMDB style API |
omdbUrl |
https://api.amodb.dev/omdb/ |
OMDb endpoint |
imageBaseUrl |
https://img.amodb.dev/t/p/ |
Base URL used by imageUrl() |
language |
none | Default language tag |
timeout |
15 |
Seconds per request |
maxRetries |
3 |
Retries after HTTP 429, from 0 to 3 |
auth |
bearer |
bearer or query |
transport |
curl | Object that implements Amodb\Http\TransportInterface |
sleep |
real sleep | Callable that takes float seconds, useful in tests |
Unknown option names raise InvalidArgumentException.
The default transport uses ext-curl. To use your own PSR-18 client, install any PSR-18 client and PSR-17 request factory, then wrap them:
use Amodb\Client;
use Amodb\Http\Psr18Transport;
// $psr18 implements Psr\Http\Client\ClientInterface
// $factory implements Psr\Http\Message\RequestFactoryInterface
$amodb = new Client($key, [
'transport' => new Psr18Transport($psr18, $factory),
]);The package only suggests psr/http-client and psr/http-factory, and nothing loads Psr18Transport unless you use it. PSR-18 has no per request timeout, so the timeout option does not apply here. Set the timeout on your own client.
You can also write a transport: implement TransportInterface::send() and return a TransportResponse.
Plans differ only in requests per minute: lite 20, pro 40, proplus 120. Every plan gets all data. Live names and prices are at https://api.amodb.dev/plans, and $amodb->plans() returns the same JSON. The API sends X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on every keyed call.
composer install
composer testThe test suite was written for PHPUnit 10 on PHP 8.1 or newer. It uses a fake transport, so it needs no network and no API key.
- Website: https://amodb.dev
- Docs: https://amodb.dev/docs
- Pricing: https://amodb.dev/pricing
- Status: https://amodb.dev/status
- Issues: https://github.com/AmoDataBase/amodb-php/issues
- Changelog: https://github.com/AmoDataBase/amodb-php/blob/main/CHANGELOG.md
- Security: https://github.com/AmoDataBase/amodb-php/blob/main/SECURITY.md
- Organization: https://github.com/AmoDataBase
MIT. See LICENSE.