Install
openclaw skills install @tracerman/cheapchartsLook up digital movie and TV deals, prices, charts, and recommendations on iTunes/Apple TV, Amazon, Vudu, and Google Play via the free CheapCharts public API (no auth or API key). Produces a markdown deal table with an all-time-low (ATL) flag per title using the bundled parallel script. Use when the user asks about movie/TV prices, sales, price drops, price history, all-time lows, or what's cheap to buy or rent right now.
openclaw skills install @tracerman/cheapchartsA free, public-API price tracker for digital movies and TV shows across iTunes (Apple TV), Amazon Prime Video, Vudu, and Google Play. No authentication. Parallel calls are safe - the bundled
deals.pyscript uses 8 concurrent DetailData workers.
Repo: https://github.com/tracerman/cheapcharts-skill
API Base URL: https://buster.cheapcharts.de/v1/gptapi/
ATL check: https://buster.cheapcharts.de/v1/DetailData.php (unofficial internal endpoint - Pitfall #17)
Don't use for: physical media (Blu-ray/DVD), streaming subscription catalogs (Netflix/Disney+), movie reviews (web search instead), video games (no public API - see EXTRAS.md).
scripts/deals.py (stdlib-only, Python 3.9+) pulls Deals, enriches every candidate with the authoritative ATL flag from DetailData in parallel (8 workers, ~12s for 50 items), preserves the API's sort order, and emits a markdown table or JSON. It handles the pitfalls (status checks, DetailData vocabulary, genre validation) so you don't have to.
python scripts/deals.py # all current deals (iTunes US), ATL column
python scripts/deals.py --since 1 # only items whose price changed today
python scripts/deals.py --atl-only # only rows at their all-time low
python scripts/deals.py --title "Fight Club" # single-title ATL check
python scripts/deals.py --title "Fight Club" --history # + full price-history timeline (sale windows, floor)
python scripts/deals.py --type seasons # TV seasons (also: rentalmovies)
python scripts/deals.py --genre horror # genre filter (case-insensitive, validated)
python scripts/deals.py --max-price 4.99 --min-savings 3 --limit 30
python scripts/deals.py --sort greatestSavings # bundles dominate this sort (Pitfall #35)
python scripts/deals.py --exclude-bundles # individual movies only (they carry ratings)
python scripts/deals.py --store amazon --title "Heat" # non-iTunes: prefer --title (Pitfall #30)
python scripts/deals.py --json # machine-readable output for pipelines
Exit codes: 0 deals found, 1 no deals matched (legitimate empty result), 2 API or usage error. Failed DetailData lookups are counted and reported in the table header; if all fail, exit is 2.
Default sort is latestPricechange (freshest drops first). Output columns: Title (links to Apple TV) | Fmt | Now | Was | Save | IMDb | RT | Date | ATL | Buy | History.
python scripts/deals.py --since 1 (fall back to --since 3 if empty; CheapCharts lags Apple by hours-to-a-day, Pitfall #14).python scripts/deals.py --atl-only.python scripts/deals.py --title "<name>".status field first - status=error fails silently otherwise (Pitfall #15).| User asks... | Do this | Why |
|---|---|---|
| "Latest deals" / "today's drops" | deals.py --since 1 | latestPricechange sort + DetailData date verification |
| "Deals under $X" | deals.py --max-price X | maxPrice filters server-side |
| "Highly-rated deals" | Deals API with imdbRating/rottenTomatoesRating | Both filter server-side on Deals (NOT Recommendations, Pitfall #23) |
| "4K / Dolby Vision / Atmos on sale" | Deals API, then filter client-side | has4K=1 param is ignored (Pitfall #16) |
| "Newest releases on sale" | Deals sort=releaseDate + strip placeholder dates | Ascending only (#11); bundles carry fake 2030 dates (#12) |
| "What's popular / selling?" | Charts (one store) or Topseller (cross-store) | Topseller is the only multi-store batch endpoint |
| "How much is [title]?" | Search -> Prices (use priceFollowUpItemType) | Search resolves the IMDb ID; iTunes = Apple TV (#19) |
| "Rental price of [title]?" | Search -> Prices with itemType=rentalmovies | Rental vocabulary is empirically discovered, works on Deals/Prices |
| "Complete series deals" | Deals itemType=seasons, filter isBundle=1 client-side | Season genre filter is broken (#21) |
| "Recommend a [genre] movie" | Recommendations with a specific genre | With genre=All it returns chart data (#18) |
| "Is [title] at its ATL?" / "lowest ever?" | deals.py --title or DetailData IsLowest flags | Only DetailData exposes ATL; the flags beat parsing (#26) |
| "When was [title] on sale?" / "price history" / "when will it be on sale again?" | deals.py --title "<name>" --history | Renders the full timeline with sale windows + historical floor; predict the next window from the cadence + the seasonal calendar in EXTRAS.md |
| "What just came off a sale?" | DetailData on candidates; report priceBefore < price rows | Sale-ended rows are "next drop target" signal (#31) |
| Compare across all 4 stores | Search -> 4x Prices calls, or Topseller | Note Movies Anywhere implications (EXTRAS.md) |
Full list of 37 with evidence and dates: references/PITFALLS.md.
itemType=movies or seasons, NOT buymovies - the wrong value errors, and looks like "no data" if you skip the status check (#13).status before iterating results - errors return {"status":"error"} with an empty result shape that mimics "no deals" (#15).has4K=1 is silently ignored on Deals/Charts - filter client-side (#16).genre is broken for seasons everywhere, and unknown genre values on movies silently return EVERYTHING (#21, #22).priceHdEvolution values are absolute prices, NOT deltas - the sign is only the change direction; summing them produces garbage. For "at ATL now?" use the IsLowest flags; for timelines use --history, which parses it correctly (#26).productPageUrl/iTunesUrl/cheapChartsProductPageUrl are in the response; guessed Apple TV slugs 404 (#32).greatestSavings surfaces bundles, latestPricechange surfaces individual movies (#35).Always include cheapChartsProductPageUrl (price-history page) and the store buy link (productPageUrl/iTunesUrl from DetailData). Never guess URLs; if a field is missing, show plain text and say "store URL unavailable".
Show savings (priceBefore - price, and %) and skip rows where savings <= $0 unless reporting them as "sale ended".
Always include an ATL column when data was enriched with DetailData: ATL = at the historical floor, - = ordinary sale. Standard table:
| Title | Genre | Now | Was | Save | IMDb | ATL | Changed |
|---|---|---|---|---|---|---|---|
| Title | Genre | $X.XX | $Y.YY | $Z.ZZ (N%) | N.N | ATL | YYYY-MM-DD |
"Sale ended" rows are reportable signal (Pitfall #31): when priceBefore < price, add a Status column (on sale / sale ended / stable) instead of rendering empty cells - these are the user's "set an alert" candidates.
Ratings come from Deals candidates, not DetailData - render IMDb/RT only for individual movies (isMovieBundle == 0); bundles and seasons legitimately have none (#37).
Filter noise: Search with itemType=all returns ebooks/audiobooks/albums too - filter by mediaType unless asked otherwise.
Mention compounding savings when relevant: gift-card stacking and seasonal sale windows are in EXTRAS.md.
status == "success" checked before iterating (Pitfall #15)itemType vocabulary correct: buymovies for Deals/Charts/Prices, movies/seasons for DetailData (Pitfall #13)priceHdIsLowest/priceSdIsLowest, not parsed evolution strings (Pitfall #26)priceBefore > price, savings > $0) and sale-ended rows reported as such (Pitfalls #9, #31)cheapChartsProductPageUrl included in anything shown to the userscripts/deals.py - the parallel deal/ATL finder (primary tool; unit-tested, CI-canaried)RECIPES.md - literal curl commands for every workflow + cron prompt templatesreferences/API.md - full endpoint/parameter/enum/field referencereferences/PITFALLS.md - all 37 empirically-verified API pitfallsreferences/EXTRAS.md - gift-card stacking, Movies Anywhere, seasonal sale calendar, CheapCharts Gamesexamples/ - real output screenshots