A Swift Package for The Movie Database (TMDb) https://www.themoviedb.org
- Comprehensive API Coverage: Full support for TMDb API v3, plus v4 user authentication — 28 specialized services and 2 on-device intelligence extensions
- Append to Response: Fetch details with credits, images, videos,
and more in a single request using
append_to_response - Movie & TV Data: Details, credits, images, videos, reviews, recommendations, similar content
- Discovery & Search: Advanced filtering, multi-type search, trending content
- User Features: Account management, favorites, watchlists, ratings (requires authentication)
- Metadata: Genres, certifications, companies, collections, watch providers
- Image Generation: One-call image URLs via
client.images, which caches TMDb's image configuration for you, with size optimization, typedImageSizeselection, and convenience accessors on models - Display Formatting: Foundation
FormatStyleconformances for rendering runtimes ("2h 15m","139 min","2 hours, 15 minutes") and vote averages as percentages (e.g."85%"in English locales) - Swift 6 Ready: Full strict concurrency support with Sendable types
- Cross-Platform: iOS 16+, macOS 13+, watchOS 9+, tvOS 16+, visionOS 1+, Linux
- Automatic Retry: Opt-in retry with exponential backoff for rate limits (HTTP 429) and server errors (HTTP 5xx)
- Response Caching: On-disk HTTP caching by default on Apple platforms
(via
URLCache, honouring TMDb'sCache-Controlheaders), plus an opt-in in-memory cache with configurable TTL and entry limits - Natural-Language Search (
TMDbIntelligence): On-device "super search" — type a prompt, get movies, TV series, and people. Deterministic interpretation via Apple's Natural Language framework on every Apple platform, with Foundation Models handling fuzzier prompts on devices with Apple Intelligence (iOS/macOS/visionOS only — tvOS and watchOS stay deterministic) - Language Model Tools (
TMDbIntelligence): Drop-in Foundation ModelsTools for a conversational movie assistant — add them to aLanguageModelSessionand the model searches, fetches details, and finds streaming availability on its own (iOS/macOS/visionOS 26, watchOS 27) - Modern Swift: Async/await throughout, strongly-typed models, protocol-based architecture
| Service | Description |
|---|---|
| movies | Movie details, credits, keywords, images, videos, reviews, recommendations, similar, releases, watch providers, append-to-response |
| tvSeries | TV show details, credits, images, videos, reviews, recommendations, similar, watch providers, screened theatrically, episode groups, append-to-response |
| tvSeasons | Season-specific details, aggregate credits, credits, images, videos, translations, watch providers, append-to-response |
| tvEpisodes | Episode-specific details, credits, images, videos, translations, append-to-response |
| people | Person details, combined/movie/TV credits, images, external links, translations, append-to-response |
| search | Multi-search across movies, TV shows, people, collections, companies, keywords |
| discover | Advanced filtering for movies and TV shows with 30+ filter options |
| trending | Trending movies, TV shows, people, and all media (daily/weekly) |
| find | Find movies, TV shows, and people by external IDs (IMDb, TVDB, etc.) |
| account | User favorites, watchlist, rated items (requires authentication) |
| authentication | Session management, guest sessions, request tokens |
| v4Authentication | TMDb v4 user authentication: request tokens, approval URLs, user access tokens (requires a bearer-token client) |
| v4Lists | TMDb v4 lists: mixed movie/TV lists, private lists, per-item comments |
| genres | Genre lists for movies and TV shows |
| keywords | Keyword details and movies by keyword |
| networks | TV network details, alternative names, logos |
| watchProviders | Streaming availability by region |
| certifications | Content ratings (G, PG, R, etc.) |
| collections | Movie collection details, images, translations |
| companies | Production company details, alternative names, logos |
| lists | Custom list management (requires authentication) |
| configurations | API configuration, countries, jobs, languages, primary translations, timezones |
| changes | Track changes to movies, TV series, people, seasons, and episodes |
| credits | Credit details including person and media information |
| reviews | Review details with author and media information |
| tvEpisodeGroups | TV episode group details and episode organization |
| guestSessions | Guest session rated movies, TV series, and episodes |
| images | Fully qualified image URLs from model image paths, with the image configuration fetched once and cached |
| naturalLanguageSearch | On-device natural-language search (all Apple platforms; enhanced by Foundation Models with Apple Intelligence on iOS/macOS/visionOS 26) — requires import TMDbIntelligence |
| languageModelTools | Foundation Models tools for a LanguageModelSession movie assistant (iOS/macOS/visionOS 26, watchOS 27) — requires import TMDbIntelligence |
See the full API documentation for detailed usage.
- Swift 6.1+
- OS
- macOS 13+
- iOS 16+
- watchOS 9+
- tvOS 16+
- visionOS 1+
- Linux
Add the TMDb package as a dependency to your Package.swift file, and add it
as a dependency to your target.
// swift-tools-version:6.1
import PackageDescription
let package = Package(
name: "MyProject",
dependencies: [
.package(url: "https://github.com/adamayoung/TMDb.git", from: "19.0.0")
],
targets: [
.target(name: "MyProject", dependencies: ["TMDb"])
]
)Add the TMDb package to your Project's Package dependencies.
Natural-language search and the Foundation Models tools ship in a separate
TMDbIntelligence library, so the core TMDb product stays purely
cross-platform. Add the product and import it alongside TMDb:
.target(
name: "MyProject",
dependencies: [
.product(name: "TMDb", package: "TMDb"),
.product(name: "TMDbIntelligence", package: "TMDb")
]
)import TMDb
import TMDbIntelligence
let results = try await tmdbClient.naturalLanguageSearch.search(matching: "movies with Tom Hanks")TMDbIntelligence is an Apple-platforms library — it builds on Apple's
Natural Language and Foundation Models frameworks. A matching
TMDbIntelligenceTesting library vends its mock and samples.
The package also vends a TMDbTesting library for use in test targets. It
provides a spy + stub mock for every service protocol (each records its calls
and returns an injectable result, defaulting to believable sample data) and
.sample / .samples factories for every service return type — so you can test
code that depends on TMDb without hitting the live API. Add it to your test
target only:
.testTarget(
name: "MyProjectTests",
dependencies: ["MyProject", "TMDbTesting"]
)import TMDb
import TMDbTesting
let movieService = MockMovieService()
movieService.detailsResult = .success(.sample)
let movie = try await movieService.details(forMovie: 550)
#expect(movieService.detailsCalls.first?.movieID == 550)Create an API key from The Movie Database web site https://www.themoviedb.org/documentation/api.
Alternatively, use the v4 API Read Access Token from the same settings
page. It is sent as an Authorization: Bearer header rather than in the URL,
keeping the credential out of logs, proxies, and cache keys:
let tmdbClient = TMDbClient(bearerToken: "<your-access-token>")import TMDb
// Initialize client
let tmdbClient = TMDbClient(apiKey: "<your-tmdb-api-key>")
// Discover movies with filters
let popularMovies = try await tmdbClient.discover.movies(
sortedBy: .popularity(descending: true)
).results
// Get movie details
let fightClub = try await tmdbClient.movies.details(forMovie: 550)
print("Title: \(fightClub.title)")
if let releaseDate = fightClub.releaseDate {
// Day-precision dates are midnight GMT on the day TMDb reports, so format
// and compare them with an explicit GMT zone. `Date.formatted()` uses the
// device's zone by default, which renders the previous day west of
// Greenwich.
let dayStyle = Date.FormatStyle(date: .abbreviated, timeZone: .gmt)
print("Release Date: \(releaseDate.formatted(dayStyle))")
}
if let voteAverage = fightClub.voteAverage {
print("Rating: \(voteAverage.formatted(.voteAveragePercentage))")
}
// Search across movies, TV shows, and people
let searchResults = try await tmdbClient.search.searchAll(query: "Breaking Bad")
// Get trending movies today
let trendingMovies = try await tmdbClient.trending.movies(inTimeWindow: .day)
// Get streaming providers for a movie
let watchProviders = try await tmdbClient.movies.watchProviders(forMovie: 550)
if let usProvider = watchProviders.first(where: { $0.countryCode == "US" }) {
print("Available on: \(usProvider.watchProviders.flatRate?.map(\.name) ?? [])")
}
// Generate a poster image URL — the image configuration is fetched
// once and cached for you
let posterURL = try await tmdbClient.images.posterURL(
for: fightClub.posterPath,
size: .width(500)
)
// Resolving many at once? Fetch the configuration once and use its
// synchronous helpers
let imagesConfiguration = try await tmdbClient.images.imagesConfiguration()
let posterURLs = popularMovies.map {
$0.posterURL(using: imagesConfiguration, size: .width(500))
}By default, the TMDb client automatically uses your system's language and
country settings from Locale.current:
import TMDb
// Uses system locale automatically (recommended)
let tmdbClient = TMDbClient(apiKey: "<your-api-key>")You can also configure the client with custom language and country settings:
// Custom configuration
let configuration = TMDbConfiguration(
defaultLanguage: "es-ES", // ISO 639-1 language code
defaultCountry: "ES" // ISO 3166-1 country code
)
let tmdbClient = TMDbClient(apiKey: "<your-api-key>", configuration: configuration)
// Disable locale defaults (API determines language)
let tmdbClient = TMDbClient(apiKey: "<your-api-key>", configuration: .default)Per-request overrides are always available:
// Override language for a specific request
let movieInFrench = try await tmdbClient.movies.details(forMovie: 550, language: "fr")Enable automatic retry with exponential backoff for transient errors. By default this retries rate limits (HTTP 429), server errors (HTTP 5xx) and transient network failures (timeouts, dropped connections, DNS failures):
// Use default retry (3 retries, exponential backoff)
let configuration = TMDbConfiguration(retry: .default)
let tmdbClient = TMDbClient(apiKey: "<your-api-key>", configuration: configuration)
// Custom retry configuration: retry rate limits and transient network errors
let retryConfig = RetryConfiguration(
maxRetries: 5,
initialDelay: .seconds(2),
retryableErrors: [.rateLimit, .networkErrors]
)
let tmdbClient = TMDbClient(
apiKey: "<your-api-key>",
configuration: TMDbConfiguration(retry: retryConfig)
)On Apple platforms the default client already caches responses on disk via
URLCache, honouring TMDb's Cache-Control and ETag headers — so repeated
requests are served from disk (and persist across launches) with no
configuration. To tune or disable it, supply your own HTTPClient backed by a
URLSession you configure.
For an additional in-memory layer (or for caching on Linux, where
URLCache is not installed), enable in-memory response caching:
// Use default caching (1-hour TTL, 100 entries)
let configuration = TMDbConfiguration(cache: .default)
let tmdbClient = TMDbClient(apiKey: "<your-api-key>", configuration: configuration)
// Custom cache configuration
let cacheConfig = CacheConfiguration(
defaultTTL: .seconds(1800), // 30-minute TTL
maximumEntryCount: 200
)
let tmdbClient = TMDbClient(
apiKey: "<your-api-key>",
configuration: TMDbConfiguration(cache: cacheConfig)
)
// Combine retry and caching
let tmdbClient = TMDbClient(
apiKey: "<your-api-key>",
configuration: TMDbConfiguration(retry: .default, cache: .default)
)let movie = try await tmdbClient.movies.details(forMovie: movieId)
let credits = try await tmdbClient.movies.credits(forMovie: movieId)
let images = try await tmdbClient.movies.images(forMovie: movieId)Compose filters fluently with copy-returning builder methods. Each method returns a new filter, so filters can be built up incrementally without mutating shared state:
let filter = DiscoverMovieFilter()
.withGenres([28, 12]) // Action AND Adventure
.voteAverage(in: 7...10)
.primaryReleaseYear(.on(2024))
let movies = try await tmdbClient.discover.movies(
filter: filter,
sortedBy: .popularity(descending: true)
)Multi-valued parameters such as genres and keywords can be joined with
logical AND (the default) or OR using DiscoverFilterJoin:
// Match movies tagged with genre 28 OR genre 12
let filter = DiscoverMovieFilter().withGenres([28, 12], joinedBy: .or)let providers = try await tmdbClient.movies.watchProviders(forMovie: movieId)
if let usProvider = providers.first(where: { $0.countryCode == "US" }) {
print("Available on: \(usProvider.watchProviders.flatRate?.map(\.name) ?? [])")
}Iterate through all pages of paginated results using AsyncSequence without manual pagination:
// Iterate through all popular movies across all pages
for try await movie in tmdbClient.movies.allPopular() {
print(movie.title)
// Automatically fetches next page when needed
}
// Early break stops fetching additional pages
var count = 0
for try await movie in tmdbClient.movies.allTopRated() {
count += 1
if count >= 50 { break }
}
// Iterate through entire pages with metadata
for try await page in tmdbClient.movies.allPopularPages() {
print("Page \(page.page ?? 0) of \(page.totalPages ?? 0)")
for movie in page.results {
print(" - \(movie.title)")
}
}
// Opt in to prefetching: the next page is fetched concurrently as the current
// page is consumed, hiding inter-page latency on long scans.
for try await movie in tmdbClient.movies.allPopular().prefetchingNextPage() {
print(movie.title)
}prefetchingNextPage() is opt-in: it trades at most one extra (possibly wasted)
request on an early break for lower latency, and the emitted items are
identical to the default lazy sequence.
Available for all paginated endpoints across 11 services: MovieService
(8 endpoints), SearchService (7 endpoints), TrendingService (4 endpoints),
TVSeriesService (8 endpoints), PersonService (2 endpoints),
DiscoverService (2 endpoints), ListService (1 endpoint), AccountService
(8 endpoints), GuestSessionService (3 endpoints), KeywordService
(1 endpoint), and ChangesService (3 endpoints). Total: 47 paginated endpoints
with 94 auto-pagination methods.
Account features require an authenticated Session. Create one with the
AuthenticationService, then bundle it with the account ID into an
AuthenticatedSession:
// Authenticate the user and create a session
let token = try await tmdbClient.authentication.requestToken()
let authURL = tmdbClient.authentication.authenticateURL(for: token)
// Present authURL to the user to approve the token, then:
let session = try await tmdbClient.authentication.createSession(withToken: token)
// Bundle the account ID and session into one value
let authenticatedSession = try await tmdbClient.account.authenticatedSession(for: session)
// Add a movie to favourites
try await tmdbClient.account.addFavourite(
movie: movieID,
authenticatedSession: authenticatedSession
)
// Rate a movie
try await tmdbClient.movies.addRating(8.5, toMovie: movieID, session: session)
// Get the movie watchlist
let watchlist = try await tmdbClient.account.movieWatchlist(
authenticatedSession: authenticatedSession
)Documentation and examples of usage can be found at https://adamayoung.github.io/TMDb/documentation/tmdb/
- TMDb API Documentation
- Swift Package Index
- Full API Reference
- Getting Started Guide
- Image URL Generation Guide
Xcode 26+ (CI builds on Xcode 27) Swift 6.1+ Homebrew
Note the higher Xcode floor applies to building this repo, not to using it:
make ci's documentation build resolves DocC links to TMDbIntelligence's
language-model tools, which compile only against the FoundationModels SDK
(Xcode 26+). Consuming the package needs just Swift 6.1 / Xcode 16.3, as under
Requirements above.
Install homebrew and the following formulae
brew install swiftlint swiftformat markdownlint xcsiftSee CLAUDE.md for comprehensive development guidelines including:
- Testing requirements (unit and integration tests)
- Code style enforcement with swift-format
- DocC documentation requirements
- Complete CI check commands
Quick reference:
make format # Auto-format code
make lint # Check code style
make test # Run unit tests
make ci # Full CI validationImportant: Both unit tests AND integration tests must pass. Integration tests require these environment variables:
TMDB_API_KEY- Your TMDb API keyTMDB_USERNAME- Your TMDb usernameTMDB_PASSWORD- Your TMDB password
The v4 suites need two further credentials, and skip silently without them:
TMDB_API_READ_ONLY_TOKEN- Your API Read Access Token, from the same TMDb settings page as the API key. This is the application's bearer credential — the v4 endpoints reject an API key.TMDB_API_USER_TOKEN- A user access token, minted through the v4 approval flow (see the Authenticating with the v4 API article). Required for anything touching a user's lists.
Running unit tests on Linux requires Docker to be running.
This repository ships a suite of
Claude Code skills (in .claude/skills/)
that automate the development workflow. Invoke any of them with /<name>.
| Skill | Purpose |
|---|---|
/deliver |
Orchestrate the full pipeline from an approved plan to a ready-to-merge PR — or /deliver next / /deliver issue <n> to select an issue (top of the Ready queue, or the one you name) and plan it first |
/review-plan |
Adversarially review the current plan with three independent critics and apply the consensus |
/implement-plan |
Implement the plan test-first (Canon TDD) until the test list is empty |
/review-changes |
Review the working-tree changes — one reviewer, or a parallel fan-out with adversarial verification for large diffs |
/capture-knowledge |
Record durable learnings (gotchas, API quirks, ADRs) into knowledge/ |
/review-knowledge |
Audit knowledge/ and .claude/ for staleness with four adversarial auditors (two lenses over two trees) that cross-examine and reach a consensus |
/triage-issues |
Groom the project board's Backlog: re-verify each issue against current main with a read-only fan-out, then close, promote to Ready with priority/size/order, or name the decision it needs |
/cut-release |
Work out the next SemVer version from the evidence, do the pre-tag housekeeping, draft release notes, then tag and publish — stopping for approval before anything is published |
/pr |
Create a pull request (/format → make ci → review → open) |
/watch-pr |
Watch the PR: resolve review threads, fix failing checks, optionally merge |
/review-pr-threads |
Resolve the PR's unresolved review threads in one sweep |
/fix-pr-checks |
Fix the PR's failing CI checks in one sweep |
The build/test rows delegate to the shared tooling-runner agent (pinned to
Haiku) to keep the main context lean; /lint and /format run make
directly.
| Skill | Purpose |
|---|---|
/build |
Compile the package for the current platform |
/build-for-testing |
Compile the package and all test targets without running them |
/test |
Run the unit tests (Swift Testing) |
/integration-test |
Run the live-API integration tests |
/lint |
Check swiftlint + swiftformat compliance |
/format |
Auto-format with swiftlint + swiftformat |
| Skill | Purpose |
|---|---|
/diagnose-ci-failure |
Diagnose a failing CI job and propose a fix |
/diagnose-integration-failure |
Diagnose a failing integration-test run and propose a fix |
/fix-integration-failures |
Diagnose and fix a failing scheduled/standalone Integration run — re-run transients, or fix real drift on a branch off main and open a PR |
/canon-tdd |
Drive test-first development (test list → failing test → pass → refactor) |
/document-swift |
Write DocC documentation for public API per project conventions |
Four subagents back the pipeline: code-reviewer (deep Swift/TMDb review,
pinned to Opus), documentation-writer (bulk DocC generation, pinned to
Sonnet), tooling-runner (build/test execution, pinned to Haiku), and
check-diagnoser (PR-check diagnosis for /fix-pr-checks — reports, never
fixes; pinned to Haiku, with a repeat re-diagnosed on Opus via the caller's
call-site override). The
reviewer follows the shared spec in
.github/CODE_REVIEW.md.
Multi-agent fan-outs run as Workflow scripts. Most are embedded in the skill
that owns them (/review-plan, /review-changes, /review-knowledge), since
each runs once per invocation. A script invoked many times within one run
lives in .claude/workflows/ instead — currently
deliver-panel.js, which resolves /deliver auto's unattended decisions — so
that an executed script cannot drift between invocations the way one
re-authored from prose can.
The live-API integration suite runs on a weekly schedule
(.github/workflows/integration.yml,
Sunday 00:00 UTC). When that scheduled run fails,
.github/workflows/integration-failure.yml
invokes /fix-integration-failures headless: it diagnoses the failure,
re-runs a transient, or fixes real drift (a TMDb backend/shape change or a
stale assumption) on a branch off main and opens a PR for review (it
never auto-merges), then files/updates a tracking issue linking the fix. See
the skill for the headless contract and the INTEGRATION_FIX_PR_TOKEN secret
it needs.
To build a feature end-to-end, draft and approve a plan in Claude Code plan
mode (or with the Plan agent — there is no /plan skill), then run
/deliver to carry it all the way to a ready-to-merge pull request.
Invoking /deliver is itself the plan-approval gate — it then runs
autonomously to a single hard stop, ready-to-merge, and ends with a short
retrospective.
Working from the issue tracker instead? A selection run needs no plan.
/deliver next takes the top startable issue off the project board's
Ready column; /deliver issue 480 takes the issue you name. Either way
it re-verifies against origin/main, claims it, drafts a plan and shows it to
you for approval, then runs the same pipeline. See Picking the issue to work
on below.
plan mode ← you draft AND approve the plan
│
▼ invoking /deliver = plan approval; it then runs autonomously:
├─ (feature branch)
├─ /review-plan 3 critics harden the plan (risky/large changes only)
├─ /implement-plan Canon TDD → empty test list (unit + integration green)
├─ /review-changes review + fix Critical/High (test-first; auto lite/full)
├─ /capture-knowledge record learnings into knowledge/
├─ /pr reviewed make ci gate → open the PR (red gate? triage, not stall)
├─ /watch-pr resolve threads + fix checks ── GATE: ready-to-merge
└─ retrospective append to knowledge/delivery-retros.md
- The one gate —
/deliverstops at a green, ready-to-merge PR; you perform the final merge (or passmergeto have it squash-merge once green). - Auto-scaled — mechanical changes take a lite path (skip the 3-critic plan review, single-reviewer code review); risky/large ones get the full machinery.
- Red-gate triage — a CI failure unrelated to your diff (e.g. a flaky live
integration test) is routed to
/fix-integration-failuresrather than stalling the delivery.
Each step is also usable on its own — e.g. /review-changes to review local
changes, or /watch-pr to babysit an existing PR.
/deliver chooses its issue under one of two selection policies. Both then
run the identical path — re-verify, claim, draft, approve, deliver:
| Policy | Invocation | Chooses |
|---|---|---|
top-of-run-list |
/deliver next |
the top startable issue on the board's Ready column |
explicit |
/deliver issue 480 |
the issue you name — and it may sit in Backlog |
/deliver next pick + plan + approve, then the pipeline above
/deliver auto next same, unattended — jurors rule instead of you
/deliver auto merge next same, and squash-merge once green
/deliver issue 480 deliver that issue specifically
/deliver auto issue 480 same, unattended
issue takes one operand, with an optional #. It is a keyword rather than a
bare number on purpose: a bare number would be ambiguous against a plan target
whose first word happens to be a number, and pinning that would need a parser.
/deliver next is /triage-issues' consumer — that skill grooms the
Backlog into an ordered Ready queue and publishes the order as a Project status
update. Selection reads that update's canonical run-list line, drops anything no
longer open-and-Ready, and re-verifies the head candidate against
origin/main before planning it — a Ready verdict was true at some sha, not
necessarily at today's. A candidate whose claims no longer hold goes back to
Backlog with a comment, and the run moves on.
/deliver issue <n> skips the queue entirely: no run-list, no ordering, no
Ready requirement, because choosing the issue yourself is the triage
judgement. It still re-verifies the issue at origin/main and still claims it.
The differences are in what happens when something is wrong — with no queue to
move along, a failed §2 filter or a failed re-verification becomes a stop
with a report rather than a pass-over, and re-verification stops rather than
demoting the issue you just chose. The merge-mode refusals are the exception
and behave differently again; see the constraint below.
That line is assembled by Scripts/build_run_list.py, not written by hand:
it carries the whole of /triage-issues' ordering — dependency order,
contention spacing, and the promotion in which a P2 that unblocks a P0 goes
first — and a re-worded line would lose exactly that while still looking
correct.
Four constraints are worth knowing before you reach for it:
mergerefuses a breaking change — and equally a reflexive one (a change to the repo's own skills, agents or workflows) or an issue written by someone outside the repo. Inmergemode an issue is selectable only if itsBreaking classisnone— absent or unparseable counts as "needs a decision". Undernextthe candidate is passed over; underissue <n>there is nothing to pass over to, so the run drops themergeopt-in and stops at the ready-to-merge gate for you instead. Either way the compatibility call reaches a human, which is where it belongs.- No run-list line, no unattended run. If no status update carries the
line,
/deliver nextfalls back to ordering by Priority then Size and says loudly that contention spacing and dependency-driven promotion are unavailable — butautoandauto mergestop and send you to/triage-issues, since a warning nobody reads is not a warning. The fallback is degraded, not unsafe: an open dependency still rejects a candidate outright. - It needs the user-scoped Projects MCP, so it runs in your own environment
(including a CCR-triggered session) but not on a GitHub Actions runner,
where that MCP is not mounted — the same limit
/triage-issueshas. - The issue is claimed at the pick, not at the worktree, which narrows the
window in which two concurrent runs could take the same one down to the
verification call (the board has no compare-and-swap, so it isn't zero).
If the run stops before its PR opens the claim is released back to the column
it came from —
Readyfor anextpick, and possibly Backlog for a named issue — and a run that dies without stopping has its claim released by the next run's reconcile sweep.
- The Movie Database (TMDb) for providing the comprehensive movie and TV data API
- JustWatch for watch provider data
- All contributors who have helped improve this library
Disclaimer: This product uses the TMDb API but is not endorsed or certified by TMDb.
This library is licensed under the Apache License 2.0. See LICENSE for details.