# Letterboxd API Scraper (`rl1987/letterboxd-api-scraper`) Actor

Scrape Letterboxd public film data: search & browse films, full film detail (genres, directors, rating, description, poster), popular reviews, list films, and optional statistics (watches/likes/ratings/fans + rating histogram). Read-only, guest access, no account.

- **URL**: https://apify.com/rl1987/letterboxd-api-scraper.md
- **Developed by:** [R.L.](https://apify.com/rl1987) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.54 / 1,000 film rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Letterboxd Scraper

Scrape public film data from [Letterboxd](https://letterboxd.com) straight from its official
mobile API — **search & browse films, full film detail, popular reviews, and the films in any
list**, with optional per-film **statistics**. Read-only, **guest access, no account** (the actor
mints Letterboxd's own anonymous app token).

### Modes

| Mode | Returns | Key inputs |
|------|---------|-----------|
| **Search films** | Films matching a query | `query` |
| **Browse films (by genre)** | Popular films, narrowed by any of the filters | `genre`, `decade`, `year`, `country`, `language` |
| **Film details (by ID)** | Full records for given films | `films`, `filmIds` |
| **Film reviews (by ID)** | Popular reviews for one film | `filmId`, `reviewsSort` |
| **List films (by list ID)** | The films in a Letterboxd list | `listId` |

Films and lists can be given by title, Letterboxd URL (`https://letterboxd.com/film/parasite/`)
or `boxd.it` short link (`https://boxd.it/18kE`) — the raw `filmIds` / `filmId` / `listId` fields
stay available as power-user overrides.

#### Browse filters

All five narrow the same popular-films feed and combine freely. `decade` is a decade's first
year (`1990`, not `1990s`), `country` is an ISO 3166-1 alpha-2 code (`KR`, `JP`) matched against
a film's production countries, and `language` is ISO 639-1 (`ko`, `ja`) matched against its
spoken languages. Leave them all empty for the unfiltered popular feed.

Turn on **Include film statistics** to add average rating and counts of watches, likes, ratings,
fans, lists and reviews (plus the rating histogram) to every film returned. It is one extra API
request per film, charged as its own event. If that request fails, the film is still delivered
and still billed — as a plain film row, without the statistics columns.

### Output

**Film rows:** `film_id`, `name`, `original_name`, `year`, `runtime`, `tagline`, `description`,
`rating`, `genres`, `directors`, `countries`, `languages`, `themes`, `top250_position`,
`poster_url`, `letterboxd_url`, `adult`. With statistics: `watches`, `likes`, `ratings_count`,
`fans`, `lists_count`, `reviews_count`, `rating_histogram`.

**Five of those come only from `Film details`.** Search, Browse and List films read Letterboxd's
film *summary*, which does not carry them, so `tagline`, `description`, `countries`, `languages`
and `themes` are null on every row of those three modes (measured across five runs,
2026-11-01). Everything else is filled in every mode. To get the full record, take the
`film_id` values from a Search or Browse run and feed them into `Film details`.

**Review rows:** `review_id`, `film_id`, `film_name`, `film_year`, `rating`, `review`,
`contains_spoilers`, `like_count`, `comment_count`, `when_created`, `when_updated`, `author`,
`author_username`, `review_url`.

#### Two columns worth reading carefully

- **`rating` is on Letterboxd's 0-5 scale**, to full precision — `4.5142172380887375` for
  *Parasite*, which the site rounds to the 4.5 stars you see. On a review row it is that
  member's own star rating, and `null` for a review left without one.
- **`top250_position` is a Top *500* rank**, despite the name. It passes through Letterboxd's
  own legacy field, and measured over the official list on 2026-11-01 it carries values up to
  400 — films at ranks 251, 301 and 400 all have one. Filtering on "not null" gives you 500
  films, not 250. The column keeps the upstream's name so existing consumers do not break.

### Notes

- Letterboxd film/list IDs are short base62 strings (e.g. `2a9q` = *Fight Club*). Get them from a
  Search or Browse run, then feed them into the detail / reviews / list modes.
- The API is not bot-gated; a proxy is optional.
- Review text is plain text with its paragraphs kept as blank lines. It comes from Letterboxd's
  own markup rather than the HTML copy of the same review, so punctuation arrives as
  punctuation (`they're`, not `they&#039;re`).
- `maxItems` counts **rows**, and a statistics-enriched film is still one row.

### Pricing

The current price of every event is on the **Pricing** tab of this Actor's page in Apify Store. That tab is generated from the Actor's own pricing record, so it is always current, and your plan's rung is applied automatically. A figure repeated here would be a second copy that drifts out of date.

You can cap what any single run may spend with **Max total charge**.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. Search and Browse return films; Film details fetches full records for given films; Film reviews returns popular reviews for one film; List films returns the films in a Letterboxd list. Default: Browse films (returns popular films with no other input needed).

## `includeStatistics` (type: `boolean`):

For every film returned, also fetch its statistics — average rating and counts of watches, likes, ratings, fans, lists and reviews, plus the rating histogram. Adds one request per film.

## `query` (type: `string`):

Search mode: film title or keywords (e.g. 'fight club').

## `genre` (type: `string`):

Browse mode: filter films by genre. Sent to Letterboxd as <code>includeGenre</code> — the obvious <code>genre</code> parameter is validated and then ignored by the API.

## `decade` (type: `string`):

Browse mode (optional): e.g. '1990' for the 1990s.

## `year` (type: `string`):

Browse mode (optional): a specific release year, e.g. '1999'.

## `country` (type: `string`):

Browse mode (optional): ISO 3166-1 alpha-2 country code, e.g. <code>KR</code> for South Korea or <code>JP</code> for Japan. Matches a film's production countries.

## `language` (type: `string`):

Browse mode (optional): ISO 639-1 language code, e.g. <code>ko</code> for Korean or <code>ja</code> for Japanese. Matches a film's spoken languages.

## `films` (type: `array`):

Film details mode: one or more films by title (e.g. 'Parasite'), Letterboxd URL (e.g. https://letterboxd.com/film/parasite/) or boxd.it short link (e.g. https://boxd.it/18kE). If left empty, the Search query field is used as a single film.

## `film` (type: `string`):

Film reviews mode: the film whose reviews to fetch — by title, Letterboxd URL or boxd.it short link.

## `list` (type: `string`):

List films mode: the list whose films to fetch — by name or Letterboxd URL.

## `reviewsSort` (type: `string`):

Film reviews mode: how to order reviews.

## `maxItems` (type: `integer`):

Roughly how many items to return (0 = no limit). Pages are up to 100.

## `maxConcurrency` (type: `integer`):

Max requests processed in parallel.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked failed.

## `maxSessionRotations` (type: `integer`):

Retries for a blocked/rate-limited status, each with a fresh session/proxy.

## `filmIds` (type: `array`):

Optional advanced override for Film details mode: raw Letterboxd film IDs (e.g. '2a9q'). If set, skips name/URL resolution.

## `filmId` (type: `string`):

Optional advanced override for Film reviews mode: a raw Letterboxd film ID.

## `listId` (type: `string`):

Optional advanced override for List films mode: a raw Letterboxd list ID.

## `proxyConfiguration` (type: `object`):

Optional. Letterboxd's API is not bot-gated; a proxy is not required.

## Actor input object example

```json
{
  "mode": "browse",
  "includeStatistics": false,
  "query": "Parasite",
  "decade": "",
  "year": "",
  "country": "",
  "language": "",
  "films": [],
  "film": "",
  "list": "",
  "reviewsSort": "ReviewPopularity",
  "maxItems": 100,
  "maxConcurrency": 5,
  "maxRequestRetries": 5,
  "maxSessionRotations": 10,
  "filmIds": [],
  "filmId": "",
  "listId": "",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "Parasite",
    "decade": "",
    "year": "",
    "country": "",
    "language": "",
    "films": [],
    "film": "",
    "list": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("rl1987/letterboxd-api-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "query": "Parasite",
    "decade": "",
    "year": "",
    "country": "",
    "language": "",
    "films": [],
    "film": "",
    "list": "",
}

# Run the Actor and wait for it to finish
run = client.actor("rl1987/letterboxd-api-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "Parasite",
  "decade": "",
  "year": "",
  "country": "",
  "language": "",
  "films": [],
  "film": "",
  "list": ""
}' |
apify call rl1987/letterboxd-api-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rl1987/letterboxd-api-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DotgtrEebK43S29yf/builds/gP5L9sykxScwv1SzJ/openapi.json
