On this page
Your Home Assistant already knows the weather, the lights and the bins. This tutorial teaches it the programme of your local cinema: a sensor that holds the day's showtimes, a dashboard card that lists the ones still to come with their booking links, and a notification at five o'clock when something is on tonight. Everything is YAML pasted into Home Assistant; no custom integration, no HACS, no add-on.
Today at Le Champo
Affreux, sales et méchants
15:40 VOST · 19:55 VOST
Safe
15:45 VOST
LA MAISON AU PIED DU ROC
20:00 VOSTWhat you need
- Home Assistant 2024.10 or later, with access to
configuration.yaml(the File editor or Studio Code Server add-on, or SSH). This tutorial was run on Home Assistant 2026.11. - 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 100 requests a week; this setup uses about 42.
- The
slugof the cinema. Search it by name:
curl "https://api.api-cinema.com/v1/cinemas?q=champo" -H "Authorization: Bearer ac_live_…"The answer gives "slug": "le-champo-paris-5e-arrondissement". Any cinema of the API works: France, the United States, Australia.
The key in secrets.yaml
Home Assistant keeps passwords out of the configuration in secrets.yaml, next to configuration.yaml. Add one line, with the word Bearer, a space, then your key:
api_cinema_auth: Bearer ac_live_…The sensor
Two blocks go in configuration.yaml. The first is a rest_command: a named HTTP request that Home Assistant can run, here the showtimes of one cinema for today, with the film of each showtime (include=movie). The second is a template sensor that runs that request when Home Assistant starts and every four hours, and keeps a short list of the day's showtimes: time, film, version, booking link.
rest_command:
cinema_showtimes:
url: 'https://api.api-cinema.com/v1/cinemas/le-champo-paris-5e-arrondissement/showtimes?date={{ now().date() }}&include=movie&limit=1000'
headers:
Authorization: !secret api_cinema_auth
timeout: 20
template:
- triggers:
- trigger: homeassistant
event: start
- trigger: time_pattern
hours: /4
minutes: 1
actions:
- action: rest_command.cinema_showtimes
response_variable: answer
continue_on_error: true
sensor:
- name: Cinema today
unique_id: cinema_today
icon: mdi:filmstrip
unit_of_measurement: showtimes
state: >
{% if answer is defined and answer.status == 200 %}
{{ answer.content.data | count }}
{% else %}
{{ this.state }}
{% endif %}
attributes:
showtimes: >
{% if answer is defined and answer.status == 200 %}
{% set ns = namespace(list=[]) %}
{% for s in answer.content.data %}
{% set ns.list = ns.list + [{
'time': s.startsAt[11:16],
'film': s.movie.title,
'version': s.version if s.format == '2D' else s.version ~ ' ' ~ s.format,
'url': s.bookingUrl
}] %}
{% endfor %}
{{ ns.list }}
{% else %}
{{ this.attributes.showtimes | default([]) }}
{% endif %}Replace the slug in the URL with your cinema's. If your configuration.yaml already has a template: key, add the - triggers: item under the existing one instead of writing a second template:.
A few details that matter:
date={{ now().date() }}asks for the whole day in the cinema's local time. The time pattern fires at 00:01, 04:01, 08:01… so the list switches to the new day just after midnight.- The sensor never goes blank. When the request fails (no network, wrong key, a
429when the weekly quota is used up) the action does not stop the template (continue_on_error), and the state and the list keep their last values (this.state,this.attributes). Home Assistant writes the error in its log. startsAtis the cinema's wall-clock time, sostartsAt[11:16]is the time printed on the ticket (19:55), without any time zone conversion.
Check the configuration (Developer tools › YAML › Check configuration), then restart Home Assistant. In Developer tools › States, sensor.cinema_today shows the number of showtimes of the day, and its showtimes attribute the list:
showtimes:
- time: "15:40"
film: Affreux, sales et méchants
version: VOST
url: https://www.cinema-lechampo.com/film.html#/media/574?showId=34629
- time: "15:45"
film: Safe
...The dashboard card
Edit a dashboard, Add card › Markdown, switch to the code editor and paste this. The template groups the showtimes still to come by film, puts the film with the next showtime first, and links each time to the cinema's booking page:
type: markdown
title: Today at Le Champo
content: |
{% set left = state_attr('sensor.cinema_today', 'showtimes') | default([], true)
| selectattr('time', '>=', now().strftime('%H:%M')) | list %}
{% for film, times in left | groupby('film') | sort(attribute='list.0.time') %}
**{{ film }}**
{% for s in times %}{{ '[' ~ s.time ~ '](' ~ s.url ~ ')' if s.url else s.time }} {{ s.version }}{{ ' · ' if not loop.last }}{% endfor %}
{% else %}
Nothing left today.
{% endfor %}The card renders again on its own every minute, because the template uses now(): a showtime disappears once it has started, with no new request to the API. Keep content: | with a pipe: with >, YAML folds the lines and the films run into each other.
A notification when something is on tonight
Settings › Automations › Create automation › Create new automation, then ⋮ › Edit in YAML, and paste:
alias: Tonight at the cinema
triggers:
- trigger: time
at: '17:00:00'
variables:
evening: >
{{ state_attr('sensor.cinema_today', 'showtimes') | default([], true)
| selectattr('time', '>=', '18:00') | list }}
conditions:
- condition: template
value_template: '{{ evening | count > 0 }}'
actions:
- action: persistent_notification.create
data:
title: Tonight at Le Champo
message: |
{% for film, times in evening | groupby('film') | sort(attribute='list.0.time') -%}
- {{ film }}: {{ times | map(attribute='time') | join(', ') }}
{% endfor %}At five, if at least one showtime starts at 18:00 or later, a notification appears in Home Assistant:
Tonight at Le Champo
- Affreux, sales et méchants: 19:55
- LA MAISON AU PIED DU ROC: 20:00
- Rendez-vous avec la peur: 22:05No showtime tonight, no notification. To get it on your phone, replace the action with the one of the Companion app, notify.mobile_app_<your_phone>, with the same title and message. The automation reads the sensor and sends no request of its own; ⋮ › Run actions tests it at any hour.
Quota
The sensor sends one request when Home Assistant starts and one every four hours: six a day, about 42 a week, whatever the number of cards and automations that read it. The free plan covers that with room for restarts. A hours: /2 pattern doubles it; stay above two hours on the free plan.
Variants
- Several cinemas. Copy the
rest_commandand the- triggers:item, with another name (cinema_showtimes_2,Cinema today 2), and point a second card atsensor.cinema_today_2. Each cinema adds 42 requests a week. - Every cinema of a town. Use
https://api.api-cinema.com/v1/showtimes?city=Lyon&date={{ now().date() }}&include=movie,cinema&limit=1000and add'cinema': s.cinema.nameto each item of the list, then show it in the card next to the time. Lyon has some 400 showtimes a day: see the next point. - Original version only. Add
&version=VOST(orVO) to the URL; or keep the URL and filter in the card with| selectattr('version', 'search', 'VO'). - A multiplex. A cinema with 150 showtimes a day makes a list larger than the 16 KB Home Assistant keeps in its history: it logs a warning and stores the state without the list, and the card keeps working. To silence it, exclude the sensor from the recorder (
recorder: exclude: entities: [sensor.cinema_today]).
Every parameter and field is in the documentation. For the same programme as a daily e-mail, see the n8n newsletter; to ask an assistant "what's on tonight?" in plain words, the MCP server answers from Claude or Cursor.