Movie showtimes API in Python: tonight, a week in CSV, a film near you

Three Python scripts with requests and pandas: what’s on tonight in a city, a week of showtimes exported to CSV, and the next screenings of a film around a point. France and US.

3 min read

On this page
  1. What you need
  2. 1.A tiny client
  3. 2.What's on tonight
  4. 3.A week of showtimes in a spreadsheet
  5. 4.One film near me
  6. Going further

Python is the shortest path from a question to an answer: what is on tonight in my city, which films get the most screens this week, where can I see this film in original version near me. This tutorial answers the three with requests and pandas, in four small files you can run as they are.

$ python tonight.py Lyon
Tonight in Lyon: 163 showtimes, 48 films

What you need

  • Python 3.9 or later.
  • An API Cinéma key: create a free account, confirm your e-mail address, then create a key from the dashboard. The free plan gives 150 requests a week for trials and non-commercial projects; each script below costs one to three.
Terminal
python -m venv .venv && source .venv/bin/activate
pip install requests pandas
export API_CINEMA_KEY="ac_live_…"

A tiny client: api_cinema.py

Two helpers: get makes one call, all_pages walks a long list of showtimes page after page.

api_cinema.py
import os
import time

import requests

API = "https://api.api-cinema.com/v1"

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['API_CINEMA_KEY']}"


def get(path, **params):
    """One GET on the API. Waits and retries once when the per-minute limit is reached (HTTP 429)."""
    for attempt in range(2):
        res = session.get(f"{API}{path}", params=params, timeout=30)
        wait = int(res.headers.get("Retry-After", "0"))
        # per-minute limit; past the weekly quota the wait runs to Monday: give up
        if res.status_code == 429 and attempt == 0 and wait <= 60:
            time.sleep(wait)
            continue
        res.raise_for_status()
        return res.json()


def all_pages(path, **params):
    """Every row of a showtimes list, page after page, following meta.nextCursor."""
    params.setdefault("limit", 1000)
    while True:
        page = get(path, **params)
        yield from page["data"]
        cursor = page["meta"].get("nextCursor")
        if not cursor:
            return
        params["cursor"] = cursor
  • The key goes in the Authorization header, read from the environment: never commit it.
  • Past the per-minute limit (60 requests per key on the standard plans) the API answers 429 with Retry-After, the seconds to wait. Past the weekly quota it also answers 429, but the wait runs to Monday: the script stops instead.
  • meta.nextCursor is the stable way through a long result: 1,000 rows a page, no row skipped or repeated even when the programme changes in between.

What's on tonight: tonight.py

tonight.py
import sys
from collections import defaultdict
from datetime import date

from api_cinema import get

city = sys.argv[1] if len(sys.argv) > 1 else "Lyon"
today = date.today().isoformat()

# from / to are read in each cinema's local time: 18:00 means 6 pm in Lyon as in Chicago
res = get("/showtimes", city=city, **{"from": f"{today}T18:00", "to": f"{today}T23:59"}, limit=1000)

films = defaultdict(list)
for s in res["data"]:
    films[s["movie"]["title"]].append(f"{s['startsAt'][11:16]} {s['cinema']['name']} ({s['version']})")

print(f"Tonight in {city}: {len(res['data'])} showtimes, {len(films)} films\n")
for title, times in sorted(films.items(), key=lambda f: -len(f[1]))[:10]:
    print(title)
    for t in sorted(times)[:5]:
        print("   ", t)
Terminal
python tonight.py Lyon
python tonight.py Chicago

from and to accept a date or a date and time, always in each cinema's own time zone: the same script works for a French and an American city. city is an exact match on the town name, so Paris covers its arrondissements but not its suburbs.

A week of showtimes in a spreadsheet: week_csv.py

all_pages fetches every showtime of the city over 7 days (a big city fits in two or three requests), pandas flattens them into a CSV and answers two questions.

week_csv.py
import sys
from datetime import date, timedelta

import pandas as pd

from api_cinema import all_pages

city = sys.argv[1] if len(sys.argv) > 1 else "Lyon"
start = date.today()
end = start + timedelta(days=6)

rows = [
    {
        "date": s["startsAt"][:10],
        "time": s["startsAt"][11:16],
        "cinema": s["cinema"]["name"],
        "film": s["movie"]["title"],
        "tmdb_id": s["movie"]["tmdbId"],
        "version": s["version"],
        "format": s["format"],
        "booking_url": s["bookingUrl"],
    }
    for s in all_pages("/showtimes", city=city, **{"from": start.isoformat(), "to": end.isoformat()})
]

df = pd.DataFrame(rows).astype({"tmdb_id": "Int64"})  # ids stay integers despite missing ones
df.to_csv(f"showtimes-{city.lower()}-{start}.csv", index=False)
print(f"{len(df)} showtimes saved, {start} → {end}\n")

print("Most programmed films this week:")
print(df.groupby("film").size().sort_values(ascending=False).head(10).to_string())

print("\nShare of original-version screenings (VO + VOST):")
print(f"{df['version'].isin(['VO', 'VOST']).mean():.0%}")
$ python week_csv.py Lyon
1755 showtimes saved, 2026-10-31 → 2026-11-06

Most programmed films this week:
film
Moulin                    129
Quelques mots d'amour     108
…

The CSV opens in Excel, Google Sheets or a notebook. In France most cinemas publish the next week on Monday or Tuesday, for the Wednesday releases: run the script on Wednesday for a full week.

One film near me: near_me.py

Find the film by title, then ask for its showtimes around a point.

near_me.py
import sys

from api_cinema import get

title = sys.argv[1] if len(sys.argv) > 1 else "Karma"
lat, lng = 45.7640, 4.8357  # Lyon city centre: use your own coordinates

movies = get("/movies", q=title, playing="true", limit=5)["data"]
if not movies:
    sys.exit(f"No film showing matches {title!r}")
movie = movies[0]

# tmdb_id gathers every showtime of the film, whatever the title each cinema publishes
film = {"tmdb_id": movie["tmdbId"]} if movie["tmdbId"] else {"movie_id": movie["id"]}
res = get("/showtimes", **film, near=f"{lat},{lng}", radius_km=5, limit=50)

print(f"{movie['title']}: next showtimes within 5 km\n")
for s in res["data"][:15]:
    print(f"{s['startsAt'][:10]} {s['startsAt'][11:16]}  {s['version']:<5} {s['cinema']['name']}")
Terminal
python near_me.py Karma

Two cinemas may publish the same film under slightly different titles; tmdb_id (The Movie Database id) gathers them all. near=lat,lng with radius_km gives the showtimes sorted by time around any point, in a metro area that city would cut short.

Going further

  • Every week, automatically. crontab -e, then 0 9 * * 3 cd ~/showtimes && .venv/bin/python week_csv.py Lyon exports the new week every Wednesday at 9 am.
  • Original versions. Add version="VOST" (or VO) to any showtimes call for a film-club list.
  • Only what changed. For a database kept in sync, updated_since=<ISO date> returns the showtimes added, changed or removed since your last run (removed ones have removedAt set).
  • Posters and booking. Every film has posterUrl, every showtime bookingUrl.

Every parameter and field is in the documentation; the guide for France and the guide for the United States show the other searches.