Typed client for MCUAPI — Marvel Cinematic Universe movies, TV shows, characters, people, and the chronology that orders them.
No API key. No runtime dependencies. ESM and CJS. ~10 kB packed.
npm install mcuapi-clientimport { MCUAPI } from 'mcuapi-client';
const mcu = new MCUAPI();
const ironMan = await mcu.movies.get(1);
// ^? Movie
const { data, total } = await mcu.movies.list({
filter: 'title=Spider',
order: 'release_date,DESC',
limit: 5,
});Requires Node 18+ (for global fetch), or pass your own — see Options.
limit is capped at 100 server-side. .all() walks the pages for you by
following the API's own _links.next, yielding one record at a time:
for await (const character of mcu.characters.all()) {
console.log(character.name, character.played_by);
}Every resource ships HAL-style _links, so you can traverse the graph without
building paths yourself:
const movie = await mcu.movies.get(1);
const cast = await mcu.follow(movie._links!.characters!);| Call | Returns |
|---|---|
mcu.movies.list(params?) |
Paginated<Movie> |
mcu.movies.get(id) |
Movie |
mcu.movies.all(params?) |
AsyncGenerator<Movie> |
mcu.movies.characters(id) |
WithRole<Character>[] |
mcu.movies.postCreditScenes(id) |
PostCreditScene[] |
mcu.tvshows.list(params?) |
Paginated<TVShow> |
mcu.tvshows.get(id) |
TVShow |
mcu.tvshows.all(params?) |
AsyncGenerator<TVShow> |
mcu.tvshows.characters(id) |
WithRole<Character>[] |
mcu.tvshows.postCreditScenes(id) |
PostCreditScene[] |
mcu.characters.list(params?) |
Paginated<Character> |
mcu.characters.get(id) |
Character |
mcu.characters.all(params?) |
AsyncGenerator<Character> |
mcu.characters.movies(id) |
WithRole<Movie>[] |
mcu.characters.tvshows(id) |
WithRole<TVShow>[] |
mcu.people.list(params?) |
Paginated<Person> |
mcu.people.get(id) |
Person |
mcu.people.all(params?) |
AsyncGenerator<Person> |
mcu.people.characters(id) |
WithRecastOrder<Character>[] |
mcu.people.titles(id) |
PersonTitle[] |
mcu.postCreditScenes.list(params?) |
Paginated<PostCreditScene> |
mcu.postCreditScenes.get(id) |
PostCreditScene |
mcu.postCreditScenes.all(params?) |
AsyncGenerator<PostCreditScene> |
mcu.timeline.get(params?) |
TimelineGroup[] |
mcu.upcoming.list(params?) |
Paginated<UpcomingItem> |
mcu.upcoming.all(params?) |
AsyncGenerator<UpcomingItem> |
mcu.titles.list(params?) |
Paginated<TitleItem> |
mcu.titles.all(params?) |
AsyncGenerator<TitleItem> |
mcu.search.list(params) |
Paginated<SearchHit> |
mcu.search.all(params) |
AsyncGenerator<SearchHit> |
mcu.stats.get() |
Stats |
mcu.health() |
Health |
mcu.follow(link) |
whatever the link points at |
List params: page, limit, order, filter, continuity,
multiverse_designation — plus studio and is_mcu on movies and TV shows.
people.list() and postCreditScenes.list() only honour page, limit,
order, and filter; neither has continuity or multiverse_designation
columns to filter by. timeline.get()
takes multiverse. upcoming.list()/.all() take page,
limit, type ('movie' | 'tvshow'), continuity, multiverse_designation,
and is_mcu — movies and TV shows whose release_date is strictly in the
future, merged and sorted ascending; titles with no announced release date are
excluded. titles.list()/.all() take the same params as movies/tvshows
(TitleListParams, now including type) — the same movies+tvshows merge as
upcoming, but paged over the whole dataset instead of only future releases,
so undated titles are included and sorted last. stats.get() takes no
params.
WithRole<T> (the appearance-relation return types above) also carries
appeared_in?: string — the reality the appearance actually happened in. It
only differs from the title's own multiverse_designation for rare
per-appearance exceptions (e.g. the Void sequences in Deadpool & Wolverine).
box_office is a string, not a number. Postgres returns bigint as a
string to avoid precision loss, so you get "585171547". Convert deliberately:
const gross = movie.box_office ? Number(movie.box_office) : null;Most fields are nullable, including obvious ones. Recently announced titles
often have no release_date, phase, saga, box_office, or runtime yet.
The types reflect what the API really returns — verified against production, not
copied from the server's entity definitions — so the compiler will make you
handle it.
Non-2xx responses throw MCUAPIError:
import { MCUAPIError } from 'mcuapi-client';
try {
await mcu.movies.get(99999);
} catch (err) {
if (err instanceof MCUAPIError) {
err.status; // 404
err.isNotFound; // true
err.isRateLimited; // true only on 429
err.body; // Problem Details JSON, or raw text
}
}The API allows 100 requests per minute per IP and sends RateLimit-* headers.
const mcu = new MCUAPI({
baseUrl: 'http://localhost:3333', // point at your own instance
timeout: 10_000, // per-request, in ms
headers: { 'User-Agent': 'my-app/1.0' },
fetch: myFetch, // undici, a proxy agent, a test double
});Every method takes an optional { signal } for cancellation:
const controller = new AbortController();
const page = await mcu.movies.list({}, { signal: controller.signal });Successful cacheable responses carry Cache-Control: public, max-age=3600 and
an ETag; API JSON responses are also cached server-side in Redis and invalidated
after MCP data writes. Error responses use Cache-Control: no-store.
MIT. Source and issues: https://github.com/AugustoMarcelo/mcuapi
Not affiliated with Marvel, Marvel Studios, or The Walt Disney Company.