CheapCharts API 스킬
iTunes(Apple TV), Amazon Prime Video, Vudu, Google Play 전반의 디지털 영화 및 TV 프로그램을 추적하는 무료 공개 API 기반 가격 트래커입니다. 인증이 필요 없고, 속도 제한이 없습니다. 병렬 호출도 안전합니다.
deals.py스크립트는 8개의 동시 DetailData 워커를 사용합니다.
저장소: https://github.com/tracerman/cheapcharts-skill
API 기본 URL: https://buster.cheapcharts.de/v1/gptapi/
역대 최저가(ATL) 확인: https://buster.cheapcharts.de/v1/DetailData.php (비공식 내부 엔드포인트 - Pitfall #17 참조)
기본 워크플로우 (표준 패턴)
사용자가 딜, 가격 인하, 가격, 또는 "지금 가장 싼 것"에 대해 질문하면 다음 순서를 따르세요:
- 스토어 결정. 기본값은
itunes(미국)이며, 여러 스토어를 원하면itunes,amazon,vudu,googlePlay를 사용합니다. - 아이템 유형 결정. 영화는
buymovies, TV는seasons, 검색 전용은all을 사용합니다. sort=latestPricechange로 딜 가져오기 "최신" / "오늘의 할인" / "방금 변경된 항목" 질문에 사용합니다. "최고의 딜"에는sort=greatestSavings, "최신 출시작"에는sort=releaseDate를 사용합니다.- "오늘" 주장 검증 각 후보 항목에 대해 DetailData를 호출하고
priceHdLastChangeDate(또는priceSdLastChangeDate)를 확인합니다. 응답의status필드를 먼저 확인하세요 -status=error이면 호출이 실패한 것입니다(Pitfall #15 참조). - 가짜 할인 제외 (
priceBefore - price <= $1) 및releaseDate정렬 시 번들 플레이스홀더 날짜를 필터링합니다. - ATL 플래그로 보강 DetailData의
priceHdIsLowest/priceSdIsLowest를 사용하여 각 보고서에 해당 할인이 역사적 최저가인지 표시합니다. CheapCharts는 항목당 하나의 추가 호출을 수행합니다. 표준 보고서 템플릿은 프레젠테이션 가이드라인을 참조하세요.
Python 검증을 포함한 전체 "오늘의 할인" 레시피는 워크플로우 레시피 섹션에 있습니다.
사용 시기
- 사용자가 디지털 스토어의 영화/TV 프로그램 가격에 대해 질문할 때
- 사용자가 디지털 영화 딜이나 할인을 찾고 싶어할 때
- 사용자가 차트(인기/트렌드)를 탐색하고 싶어할 때
- 사용자가 장르별 추천을 원할 때
- 사용자가 여러 스토어(iTunes, Amazon, Vudu, Google Play) 간 가격을 비교하고 싶어할 때
- 사용자가 가격 내역이나 역대 최저 가격을 확인하고 싶어할 때
- 사용자가 CheapCharts를 직접 언급할 때
사용하지 말아야 할 경우: 실제 미디어(블루레이/DVD), 스트리밍 구독 카탈로그(Netflix/Disney+), 영화 리뷰/비평(대신 웹 검색 사용).
빠른 결정 가이드
| 사용자가 말하는 경우... | 사용할 엔드포인트 | 이유 |
|---|---|---|
| "최신 딜" / "오늘의 할인" / "방금 변경된 항목" | Deals + DetailData | 기본 워크플로우: sort=latestPricechange로 가져온 후 각 항목의 priceHdLastChangeDate가 오늘 날짜와 일치하는지 확인 (레시피 참조: "오늘의 가격 인하") |
| "$X 이하 딜 찾기" | Deals | maxPrice + sort=greatestSavings로 필터링 |
| "평점 높은 최고 딜" / "$X 이하 비평가 극찬작" | Deals | imdbRating + maxPrice로 필터링 (둘 다 서버 측 필터, 레시피 참조) |
| "4K / Dolby Vision / Atmos 영화" | Deals + 클라이언트 필터 | has4K=1 서버 필터는 작동하지 않음(Pitfall #16) - 가져온 후 클라이언트 측에서 필터링 |
| "할인 중인 최신 출시작" | Deals | sort=releaseDate + 번들 플레이스홀더 필터 (Pitfall #12 참조) |
| "지금 가장 인기 있는 것" / "무엇이 팔리고 있나" | Charts 또는 Topseller | 단일 스토어는 Charts, 여러 스토어는 Topseller |
| "[제목] 가격은 얼마인가요" | Search 후 Prices | Search로 imdbId 획득, 이후 여러 스토어에서 Prices 호출 (Pitfall #19: iTunes = Apple TV) |
| "최고의 시리즈 전체 딜" | Deals (seasons) | 클라이언트 측에서 isBundle=1 필터링 (레시피) |
| "[장르] 영화 추천해줘" | Recommendations | 특정 장르 + imdbRating 전달 (Pitfall #18: 장르 없이 호출 시 차트 데이터 반환) |
| "가장 많이 팔리는 것은 무엇인가" | Topseller | 여러 스토어의 베스트셀러 |
| "[제목] 검색" | Search | 제목 기반 검색, 메타데이터 반환 |
| "[제목]이 역대 최저가인가요?" / "ATL 딜" / "역대 최저 가격" | Deals + DetailData | DetailData는 priceHdIsLowest / priceSdIsLowest 플래그를 제공하므로 priceHdEvolution을 파싱할 필요 없음 (레시피 참조: "역대 최저가(ATL) 딜"). 실제 역대 최저 금액이나 이전 ATL 변경 날짜가 필요한 경우에만 priceHdEvolution 사용. |
| "방금 세일에서 제외된 것은?" / "다음 할인 대상" | 후보 집합에 대해 DetailData | priceBefore < price는 가장 최근 변경이 가격 인상(세일 종료)임을 의미합니다. 이 항목들은 단기간 내 할인 가능성이 가장 높으며, 조용히 필터링하지 말고 활성 딜과 함께 보고해야 합니다. Pitfall #31 참조. |
API 엔드포인트
공통 매개변수: Charts, Deals, Prices, Recommendations는 공통 매개변수(
action,store,country,itemType,imdbRating,rottenTomatoesRating)를 공유합니다. Search와 Topseller는 각각 고유한 매개변수 집합을 가집니다 (아래 해당 섹션 참조).
1. Search - Search.php
제목으로 항목을 찾습니다. 제목만 있고 IMDb ID가 필요할 때 먼저 사용하세요.
필수: action=search, query=<title> (또는 searchTerm=<title> - 별칭, 공급업체의 llms.txt 기준)
선택 사항: store=itunes (기본값), country=us (기본값), itemType=all (기본값), limit=20 (기본값 20, 최대 20), offset=0 (limit 초과 결과에 대한 페이지 오프셋)
(RECIPES.md 참조)
평면 목록 반환: {"status":"success","results":[...items...]}. 각 항목에는 imdbId(가능한 경우), mediaType, itemType, priceFollowUpItemType, store, country, cheapChartsProductPageUrl, title, artist, releaseDate, releaseYear, currency, price, currentPrice, has4K, hdrFormat, imdbRating, rottenTomatoesRating이 포함됩니다 (공통 항목 필드 표의 전체 세부 정보 참조).
2. Charts - Charts.php
영화 또는 TV 시즌의 현재 차트 순위.
필수: action=getCharts, store, country, itemType (buymovies 또는 seasons)
선택 사항: genre=All, quality=hd4k, limit, imdbRating (최소), rottenTomatoesRating (최소), releaseYear (예: 2025-2025)
(RECIPES.md 참조)
반환: {"status":"success","results":{"buymovies":[...]}}. 항목에 rank 필드가 포함됩니다.
3. Deals - Deals.php
현재 딜 및 가격 인하. 할인 상품 찾기에 가장 적합합니다.
필수: action=getDeals, store, country, itemType (buymovies 또는 seasons)
선택 사항: genre=All, quality=hd4k, sort=latestPricechange, maxPrice, releaseYear (형식: 2020-2025 - 단일 연도 2026-2026도 작동; 기본값: 전체 연도), limit, imdbRating (최소 평점 0-10, 기본값 0 = 필터 없음, 예: imdbRating=7), rottenTomatoesRating (최소 점수 0-100, 기본값 0 = 필터 없음, 예: rottenTomatoesRating=80), has4K=1 (작동하지 않음 - 무시됨, 대신 클라이언트 측에서 필터링)
정렬 옵션: latestPricechange (기본값), price, greatestSavings, greatestPercentageSavings, popularity, alphabetical, releaseDate (오름차순만 - Pitfall #11 참조)
날짜 필터: releaseYear=YYYY-YYYY는 단일 연도(2026-2026)와 범위(2024-2026)를 모두 허용합니다. Deals 및 Charts 엔드포인트에서 작동합니다. Recommendations, Prices 또는 Search에서는 지원되지 않습니다.
(RECIPES.md 참조)
반환: {"status":"success","results":{"buymovies":[...]}}. 항목에 price, priceBefore, imdbRating, rottenTomatoesRating이 포함됩니다.
4. Prices - Prices.php
특정 타이틀의 현재 가격을 IMDb ID로 조회합니다.
필수: action=getPrices, store, country, itemType=buymovies, imdbIDs (참고: llms.txt 공통 매개변수는 Prices에 seasons가 유효하다고 나열하지만, Prices 전용 매개변수 표와 경험적 테스트는 buymovies만 작동함을 확인함 - Pitfall #7 참조) (쉼표로 구분, 예: tt0468569,tt2911666)
(RECIPES.md 참조)
반환: {"status":"success","results":{"buymovies":[...]}}. Deals와 동일한 항목 구조.
5. Recommendations - Recommendations.php
장르와 화질로 필터링된 큐레이션 추천.
필수: action=getRecommendations, store, country, itemType (buymovies 또는 seasons)
선택 사항: genre=All, quality=hd4k, limit, imdbRating, rottenTomatoesRating
참고: llms.txt는 여기서 imdbRating/rottenTomatoesRating이 지원된다고 나열하지만, 경험적 테스트에서는 이러한 필터가 Recommendations에서 무시됨을 보여줍니다(Pitfall #23). 대신 sort=greatestSavings + 평점 필터와 함께 Deals를 사용하세요.
(RECIPES.md 참조)
반환: {"status":"success","results":{"buymovies":[...]}}. 항목에 description 필드가 포함될 수 있습니다.
6. Topseller - Topseller.php
여러 스토어의 베스트셀러. 다른 엔드포인트와 달리 itemType, imdbRating, rottenTomatoesRating이 필요하지 않습니다.
필수: action=getTopsellerForStartpage, country, store (쉼표로 구분, 예: itunes,amazon,vudu,googlePlay)
선택 사항: maxItemCount=5 (기본값 5, 스토어 및 카테고리당)
(RECIPES.md 참조)
반환: {"status":"success","results":{"itunes":{"movies":[...],"seasons":[...]},"amazon":{...}}}. 스토어별, 영화/시즌별로 그룹화됩니다. 필드 가용성은 스토어에 따라 다릅니다 - has4K, hasAtmos, hdrFormat, isMovieBundle은 iTunes 항목에만 표시됩니다 (Pitfall #3 참조).
7. DetailData (내부) - DetailData.php
공식 gptapi 문서에는 없습니다. CheapCharts 웹사이트의 네트워크 호출을 검사하여 발견되었습니다. 현재 가격, 전체 가격 내역, 번들의 하위 시즌을 포함한 전체 항목 세부 정보를 반환합니다. 영화와 시즌 모두에서 작동합니다. 브라우저 없이 시즌/번들 가격을 얻는 신뢰할 수 있는 방법입니다.
필수: store, country, itemType (movies 또는 seasons - buymovies 아님), idInStore (Search 결과의 iTunes 스토어 ID)
(RECIPES.md 참조)
반환: {"results":{"seasons":{...}}} (전달한 itemType과 일치하는 키).
주요 필드 (시즌):
| 필드 | 설명 |
|---|---|
priceSd / priceHd | 현재 SD/HD 가격 |
priceSdBefore / priceHdBefore | 마지막 변경 전 이전 가격 |
priceSdLastChangeDate / priceHdLastChangeDate | 마지막 가격 변경 날짜 |
priceSdEvolution / priceHdEvolution | 날짜:+/-가격~날짜:+/-가격~... 문자열 형식의 전체 가격 내역. + = 가격 상승, - = 가격 하락. 아래 "priceHdEvolution 파싱" 참조 - 델타 규칙은 절대 가격을 재구성하는 데 신뢰할 수 없습니다. IsLowest 플래그를 선호하세요. |
priceSdIsLowest / priceHdIsLowest | 현재 SD/HD 가격이 CheapCharts가 이 타이틀에 대해 추적한 전체 내역 중 역대 최저가와 같으면 1, 그렇지 않으면 0. 이것이 표준 ATL 확인 방법입니다 - priceHdEvolution을 파싱하는 대신 사용하세요. |
priceSdIsBest / priceHdIsBest | 현재 SD/HD 가격이 현재 세일 기간 내 최저 가격(즉, 현재 진행 중인 세일의 최저점)과 같으면 1, 그렇지 않으면 0. IsLowest와 다릅니다 - IsBest=1, IsLowest=0은 "이번 세일 중에서는 최저지만 이전 세일에서 더 낮았음"을 의미합니다. |
priceSdDropIndicator / priceHdDropIndicator | 마지막 변경 시 가격이 상승했으면 1, 하락했으면 -1, 변동 없으면 0. priceBefore와 비교하지 않고 "실제 할인"을 필터링하는 데 유용합니다. |
isBundle / isSeasonBundle | 시즌 번들 여부 |
isSeasonComplete | 모든 시즌이 포함되었는지 여부 |
childSeasonsCount | 번들의 하위 시즌 수 |
bundleSavings / bundleSavingsHd | 개별 시즌을 정가로 구매하는 것과 비교한 할인액 |
saveOpportunity / saveOpportunityHd | bundleSavings와 동일 |
episodeCount | 총 에피소드 수 |
advisoryRating | 콘텐츠 등급 (예: TV-14) |
summary | 전체 시놉시스 |
seasonFamily | 하위 시즌 배열, 각각 idInStore, title, priceSd, priceHd, priceSdEvolution, priceHdEvolution, cheapChartsProductPageUrl 포함 |
productPageUrl | 직접 Apple TV / iTunes 스토어 URL |
iTunesUrl | iTunes URL |
imageSmallUrl / imageMediumUrl / imageLargeUrl | 커버 아트 URL |
appleTvId | Apple TV 내부 ID |
priceHdEvolution 파싱 (낮은 우선순위 - priceHdIsLowest 선호): ~로 분할하면 각 세그먼트는 YYYY-MM-DD:[+/-]가격 형식입니다. 마지막 세그먼트(가장 오른쪽)가 가장 오래된 기록 가격이고, 첫 번째 세그먼트가 가장 최근 가격 변경입니다. 예:
2026-05-20:+89.99~2026-05-12:-69.99~2026-04-01:-49.99~2022-02-09:89.99
주의사항: 델타 규칙은 타이틀마다 일관적이지 않습니다. 일부의 경우 마지막 세그먼트는 부호 없는 절대 시작 가격(2022-02-09:89.99)이고, 다른 경우 델타가 현재 가격까지 깔끔하게 누적되지 않습니다. 경험적으로 가장 오래된 세그먼트에서 델타를 따라가도 항상 priceHd/priceSd를 재현하지는 않습니다. ATL 감지에는 priceHdIsLowest / priceSdIsLowest 플래그를 대신 사용하세요. 이 플래그가 권위적입니다. 역대 최저 가격의 절대 금액이나 발생 날짜가 필요한 경우에만 priceHdEvolution 파싱으로 대체하고, 신뢰하기 전에 결과를 priceHd와 대조하여 검증하세요.
열거형 값
Store
| 값 | 지원 국가 |
|---|---|
itunes | us, de, gb, fr, au, ca, at, ch, es, pt, ru, jp, tr, pl, in, cn |
amazon | us, de |
vudu | us |
googlePlay | us |
사용자가 스토어를 지정하지 않으면 itunes를 기본값으로 사용하세요. 가장 광범위한 국가를 지원합니다.
ItemType
| 값 | 의미 | 사용처 |
|---|---|---|
buymovies | 영화 (구매 가격) | Charts, Deals, Prices, Recommendations |
rentalmovies | 영화 (대여 가격, 시청 시작까지 약 30일, 완료까지 48시간) | Charts, Deals, Prices (공식 llms.txt 기준) |
seasons | TV 프로그램 시즌 | Charts, Deals, Recommendations, DetailData |
movies | 영화 메타데이터 전용 | Search 전용 |
seasons | 시즌 메타데이터 전용 | Search 전용 |
all | 모든 미디어 유형 (영화, 시즌, 전자책, 오디오북, 앨범) | Search 전용 |
구매 vs 대여 (경험적으로 발견됨, 현재 llms.txt에는 없음): Prices 엔드포인트의 itemType 매개변수는 buymovies(구매 가격) 또는 rentalmovies(대여 가격)를 허용합니다. 대여는 일반적으로 시청 시작까지 30일, 시작 후 완료까지 48시간입니다. Search 엔드포인트는 두 유형을 모두 반환할 수 있습니다 - priceFollowUpItemType 필드는 후속 Prices 호출에 사용할 유형을 알려줍니다.
Quality
| 값 | 의미 |
|---|---|
hd | HD 화질만 |
hd4k | HD 및 4K 화질 |
기본값: hd4k (명시적으로 전달하지 않으면 API가 4K를 포함하지 않음 - 기본 동작은 hd로 가정)
Genre (영화)
| 값 | 설명 |
|---|---|
All | 모든 장르 |
Action | 액션 |
Comedy | 코미디 |
Documentary | 다큐멘터리 |
Drama | 드라마 |
Family | 가족 |
Horror | 공포 |
Kids | 어린이 |
Romance | 로맨스 |
SciFi | SF |
Thriller | 스릴러 |
Western | 서부 |
Genre (TV)
| 값 | 설명 |
|---|---|
All | 모든 장르 |
Action | 액션 |
Anime | 애니메이션 |
Comedy | 코미디 |
Documentary | 다큐멘터리 |
Drama | 드라마 |
Family | 가족 |
Horror | 공포 |
Kids | 어린이 |
Reality | 리얼리티 |
SciFi | SF |
Soap | 연속극 |
Thriller | 스릴러 |
공통 항목 필드
이 표는 엔드포인트에서 반환되는 필드의 가용성을 문서화합니다. item.movies 또는 item.seasons 아래에 중첩된 결과를 반환하는 Charts 및 Topseller는 중첩 해제 후 동일한 구조를 가집니다.
| 필드 | Charts | Deals | Prices | Recommend. | Topseller | Search | DetailData | 참고 |
|---|---|---|---|---|---|---|---|---|
imdbId | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 모든 엔드포인트에서 사용 가능 |
mediaType | ✓ | ✓ | ✓ | ✓ | ✓ | "movie" 또는 "season" | ||
itemType | ✓ | ✓ | ✓ | ✓ | ✓ | "buymovies", "rentalmovies", "seasons" 등 | ||
priceFollowUpItemType | ✓ | 후속 Prices 호출에 사용할 유형 | ||||||
store | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 검색에 사용된 값을 반영 |
country | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 검색에 사용된 값을 반영 |
cheapChartsProductPageUrl | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
artist | ✓ | ✓ | ✓ | ✓ | ✓ | TV 프로그램의 경우 네트워크/방송사, 영화의 경우 공백 | ||
releaseDate | ✓ | ✓ | ✓ | ✓ | ✓ | ISO 8601 날짜 문자열 | ||
releaseYear | ✓ | ✓ | ✓ | ✓ | ✓ | |||
coverUrl / imageSmallUrl | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Search는 coverUrl 사용, DetailData는 imageSmallUrl 사용 (둘 다 표준 커버 아트 URL, Pitfall #30 참조) |
currency | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 예: "USD" | |
price | ✓ | ✓ | ✓ | ✓ | ✓ | 현재 가격 (Search에 없을 수 있음) | ||
currentPrice | ✓ | ✓ | ✓ | ✓ | ✓ | price와 동일 | ||
priceBefore | ✓ | ✓ | 마지막 변경 전 이전 가격 | |||||
has4K | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
hdrFormat | ✓ | ✓ | ✓ | ✓ | ✓ | "Dolby Vision" / "HDR10" / "" | ||
hasAtmos | ✓ | Topseller 전용 (Pitfall #3 참조) | ||||||
isMovieBundle | ✓ | Topseller 전용 (Pitfall #3 참조) | ||||||
imdbRating | ✓ | ✓ | ✓ | ✓ | ✓ | IMDb 평점 (0-10) | ||
rottenTomatoesRating | ✓ | ✓ | ✓ | ✓ | ✓ | Rotten Tomatoes 점수 (0-100) | ||
rank | ✓ | Charts 전용 | ||||||
description | ✓ | Recommendations 전용 | ||||||
idInStore | ✓ | DetailData 전용 - iTunes 스토어 ID |
기본값
각 요청에 대해 필수 매개변수와 선택적 매개변수는 아래 기본값으로 전달되지 않습니다. 기본값이 아닌 값을 사용할 때만 매개변수 보내기.
| 매개변수 | 기본값 (전달하지 않음) |
|---|---|
store | itunes |
country | us |
itemType (Search) | all |
itemType (Charts/Deals/Recommendations) | buymovies |
genre | All |
quality | hd4k |
sort (Deals) | latestPricechange |
limit | 20 (Search: 20, Topseller: 5) |
maxPrice | 0 (필터 없음) |
imdbRating | 0 (필터 없음) |
rottenTomatoesRating | 0 (필터 없음) |
알려진 함정 (Pitfalls)
Pitfall #1: Search 결과에서 price가 누락됨
증상: Search는 항목을 반환하지만 때때로(특히 대여 또는 일부 저장되지 않은 타이틀의 경우) price 및 currentPrice 필드가 누락됩니다. 이는 유효한 결과입니다. price 누락은 단순히 해당 항목에 사용 가능한 구매 가격이 없음을 의미합니다.
해결 방법: 항상 Search 결과에서 price를 선택 사항으로 처리합니다. 구체적인 가격이 필요한 경우, 필요한 경우 Prices 엔드포인트를 사용하거나 이를 무시합니다.
Pitfall #2: Search 및 Prices가 제목 21자 제한이 있는 Search를 사용할 때 "빈" 결과를 반환함
증상: Search.php에 21자 이상의 쿼리를 보내면 빈 결과가 반환됩니다. 이는 버그가 아니라 Search 엔드포인트에 적용되는 제한 사항입니다.
해결 방법: 쿼리를 공백으로 분할하고 첫 번째 단어(또는 처음 21자)만 보냅니다.
Pitfall #3: Topseller는 iTunes 이외의 스토어에서 누락된 필드가 있음
증상: Topseller 결과는 iTunes(예: has4K, hasAtmos, hdrFormat, isMovieBundle)에 비해 Amazon, Vudu, Google Play에서 적은 필드를 가질 수 있습니다.
해결 방법: 필요한 경우 이러한 필드를 선택 사항으로 처리합니다.
Pitfall #4: 모든 엔드포인트에 대해 Store 매개변수에서 대소문자 지원 없음
증상: 잘못된 케이스(예: iTunes 대신 itunes, Amazon 대신 amazon)로 Store 매개변수를 보내면 API가 오류나 빈 결과를 반환합니다.
해결 방법: 항상 소문자를 사용합니다.
Pitfall #5: 4K/HDR 데이터가 일부 스토어에 대해 불완전함
증상: 일부 스토어(특히 Vudu 및 Amazon)는 has4K, hdrFormat 등에 대해 불완전하거나 누락된 데이터를 반환할 수 있습니다.
해결 방법: 이러한 필드를 결정적인 것으로 의존하지 않습니다.
Pitfall #6: 일부 타이틀에 대해 Prices가 가격을 반환하지 않음
증상: Prices 엔드포인트가 유효한 IMDb ID에 대해 빈 결과나 가격이 없는 항목을 반환합니다.
해결 방법: 이는 유효한 시나리오입니다. 가격이 없는 항목을 필터링하고, 다른 소스에 의존합니다.
Pitfall #7: Prices 엔드포인트가 시즌에 대해 작동하지 않음
증상: 공식 llms.txt는 Prices 엔드포인트에서 itemType=seasons를 유효한 것으로 나열하지만, 경험적 테스트는 검증된 시즌 IMDb ID로 Prices를 호출할 때 빈 결과가 반환됨을 보여줍니다. 이는 시즌 가격이 Prices 엔드포인트의 공식 지원 문서와 달리 작동하지 않음을 시사합니다.
해결 방법: 시즌 가격을 얻으려면 DetailData 엔드포인트를 사용합니다(아래 DetailData 섹션 참조). DetailData는 Search에서 반환된 시즌에 대해 신뢰할 수 있는 가격 정보와 가격 내역을 제공합니다.
Pitfall #8: Search 엔드포인트에서 IMDb ID가 항상 반환되지 않음
증상: Search 결과의 일부 항목에 imdbId 필드가 없는 경우가 있습니다. 이는 드물지만 발생할 수 있습니다.
해결 방법: 결과를 표시하기 전에 Search에서 반환된 모든 항목을 imdbId 존재 여부로 필터링합니다.
Pitfall #9: 하이브리드 컬렉션/시즌 데이터가 일관성 없음
증상: 일부 항목이 하나의 Search 결과에 개별 시즌과 번들로 모두 나타납니다.
해결 방법: 중복 제거 로직을 구현합니다(예: 동일한 IMDb ID를 가진 번들을 선호하거나 시즐을 선호).
Pitfall #10: Topseller가 시즌 대신 영화를 선호함
증상: Topseller 엔드포인트는 영화 섹션과 시즌 섹션을 모두 반환하지만, 시즌 섹션은 종종 영화 섹션보다 항목이 적거나 없습니다.
해결 방법: 영화와 시즌을 별도로 처리하고 빈 시즌 섹션을 우아하게 처리합니다.
Pitfall #11: Deals의 releaseDate 정렬이 오름차순 전용임
증상: Deals 엔드포인트는 sort=releaseDate를 지원하지만 항상 오름차순(가장 오래된 것부터)으로 정렬합니다. 내림차순 정렬을 위한 reverse=true 또는 sort=-releaseDate 매개변수는 없습니다.
해결 방법: 가장 최근 항목을 얻으려면 큰 limit(예: 200)를 가져와 클라이언트 측에서 역순으로 정렬하고 가장 최근 릴리스의 처음 몇 개를 검색합니다. 또는 더 많은 항목을 얻기 위해 releaseYear를 필터링합니다.
Pitfall #12: releaseDate 정렬이 번들에 대해 0 또는 null 날짜를 반환함
증상: sort=releaseDate를 사용할 때 일부 항목(특히 번들)은 1970-01-01과 같은 플레이스홀더 releaseDate를 갖습니다.
해결 방법: 이러한 플레이스홀더 날짜를 클라이언트 측에서 필터링합니다.
Pitfall #13: DetailData 엔드포인트가 공식 문서화되지 않음
증상: DetailData는 gptapi 래퍼의 공식 문서에 나타나지 않습니다. 이는 CheapCharts 웹사이트의 네트워크 트래픽을 검사하여 발견된 내부 검색 엔드포인트입니다.
해결 방법: 이는 중요한 기능이므로 포함합니다. 문서화되지 않았으므로 엔드포인트가 변경되거나 더 이상 사용되지 않을 수 있습니다. 이에 따라 모니터링하고 적응합니다.
Pitfall #14: 최대 20개 항목으로 제한된 Search 결과
증상: Search 엔드포인트는 최대 20개의 결과를 반환합니다. 오프셋 매개변수로 페이지 매기기를 지원하지만, 검색 쿼리에 일치하는 모든 항목을 가져오는 능력이 제한됩니다.
해결 방법: 더 좁은 검색어를 사용하거나 여러 검색어를 결합합니다.
Pitfall #15: 응답이 유효한 JSON이지만 status 필드가 error임
증상: 서버가 {"status":"error","..."}와 같은 유효한 JSON을 반환할 수 있습니다. HTTP 상태 코드는 200입니다. 오류 메시지는 때때로 설명적이며(예: "Invalid store value: itunes (valid: itunes, amazon, vudu, googlePlay)"), 다른 경우 공백이거나 유용하지 않을 수 있습니다.
해결 방법: 데이터를 신뢰하기 전에 항상 응답의 status 필드가 "success"인지 확인합니다.
Pitfall #16: has4K=1 서버 측 필터가 Deals에서 작동하지 않음
증상: Deals 엔드포인트는 has4K=1을 받아들일 수 있지만 결과를 필터링하지 않습니다. 동일한 쿼리가 has4K=1 있든 없든 결과가 동일하며, 이는 이 필드가 서버 측에서 무시됨을 의미합니다.
해결 방법: 항목을 가져와 클라이언트 측에서 has4K로 필터링합니다.
Pitfall #17: DetailData의 전달자 토큰이 자주 만료됨
증상: DetailData.php에 무단 액세스를 방지하기 위해 전달자 토큰이 필요합니다. 유효한 전달자 토큰을 https://buster.cheapcharts.de/v1/DetailData.php의 응답 쿠키에서 추출할 수 있습니다. 간단한 cURL 호출로 충분합니다. 전달자 토큰이 만료되면 DetailData는 {"errorCode":403,"message":"bearer token invalid"}를 반환합니다.
해결 방법: 전달자 토큰을 가져옵니다:
BEARER_TOKEN=$(curl -sI "https://buster.cheapcharts.de/v1/DetailData.php?store=itunes&country=us&itemType=movies&idInStore=12345" | grep -i "^Set-Cookie:" | grep -oP 'PHPSESSID=\K[a-z0-9]+' | head -1)
그런 다음 DetailData 요청에 추가합니다:
curl -s "https://buster.cheapcharts.de/v1/DetailData.php?store=itunes&country=us&itemType=seasons&idInStore=12345" -H "Cookie: PHPSESSID=$BEARER_TOKEN"
Pitfall #18: Recommendations Endpoint는 장르 매개변수가 없을 때 차트 데이터를 반환함
증상: Recommendations 엔드포인트는 genre가 All로 설정되거나 전달되지 않을 때 Deals 스타일 데이터 대신 차트 스타일 데이터를 반환합니다. 이는 Recommendations API가 장르가 지정되지 않을 때 맨 위 차트 입장 데이터로 대체되기 때문입니다.
해결 방법: 추천을 요청할 때는 genre를 명시적으로 설정하거나, 사용자가 임의의 항목만 원하는 경우(예: "아무거나 추천해줘") Deals를 sort=greatestSavings + sort=popularity와 함께 사용합니다.
Pitfall #19: iTunes 스토어가 Apple TV로 표시됨
증상: 사용자가 "iTunes" 스토어에 대해 질문할 때, API 응답은 종종 항목을 "Apple TV" 대신 "iTunes"로 참조합니다. 기술적으로는 동일한 스토어이지만, 사용자가 이를 인식하지 못할 수 있습니다.
해결 방법: 가격 및 세부 정보를 표시할 때 "iTunes" 대신 "Apple TV"를 사용하고, 사용자에게 동일한 스토어임을 설명합니다.
Pitfall #20: Topseller 엔드포인트가 스토어별 누락된 필드가 있는 목록을 반환함
증상: Topseller 엔드포인트의 결과는 스토어에 따라 일관성 없는 필드 가용성을 가질 수 있습니다. 예를 들어, iTunes 항목은 has4K, hdrFormat, hasAtmos, isMovieBundle을 포함할 수 있지만 Amazon, Vudu, Google Play의 항목은 일반적으로 누락합니다.
해결 방법: 이러한 필드가 Topseller 데이터에 존재하는 경우 선택 사항으로 처리합니다.
Pitfall #21: 비-iTunes 스토어에 대한 빠진 번들 정보
증상: isBundle 및 관련 번들 정보는 iTunes/Apple TV 데이터에서만 안정적으로 사용 가능합니다.
해결 방법: 비-iTunes 스토어에 대한 번들 감지에 의존하지 않습니다.
Pitfall #22: Topseller가 시즌을 영화로 반환함
증상: Topseller 엔드포인트는 movies 키 아래 시즌을 반환할 수 있습니다. API는 Topseller 엔드포인트를 통해 가져온 항목에 대한 올바른 mediaType/itemType을 보장하지 않습니다.
해결 방법: 항상 각 항목의 mediaType 필드를 확인하여 영화와 시즌을 구분하고, 표시하기 전에 데이터를 검증합니다.
Pitfall #23: Recommendations가 imdbRating/rottenTomatoesRating을 무시함
증상: 공식 llms.txt는 Recommendations 엔드포인트에서 imdbRating 및 rottenTomatoesRating을 지원되는 매개변수로 나열합니다. 그러나 경험적 테스트는 이러한 매개변수가 서버에서 무시됨을 보여줍니다. 다른 모든 것이 동일할 때, 이 매개변수를 사용하거나 사용하지 않고 Recommendations를 호출하면 동일한 결과가 반환됩니다.
해결 방법: 필요한 심층 필터링을 위해 Deals 엔드포인트를 sort=greatestSavings + imdbRating/rottenTomatoesRating 매개변수와 함께 사용합니다(Deals 엔드포인트는 이러한 필터를 존중함).
Pitfall #24: Search Endpoint가 빈 응답을 반환함
증상: Search 엔드포인트는 일반적으로 빈 배열과 함께 {"status":"success"}를 반환합니다. 이는 기술적인 오류가 아니라 정상적인 동작이므로, 빈 결과가 아무것도 반환되지 않아야 한다는 것을 의미하지는 않습니다.
해결 방법: 빈 결과를 유효하지 않은 것으로 표시하지 않고 단순히 일치하는 항목이 없음을 나타내는 것으로 처리합니다. 사용자에게 검색어를 수정하거나 좁힐 것을 제안합니다.
Pitfall #25: 가격이 문자열로 반환됨
증상: 거의 모든 엔드포인트가 price, priceBefore 및 기타 숫자 필드를 JSON 숫자가 아닌 문자열로 반환합니다(예: "9.99" 대신 9.99). 이는 llms.txt에 문서화되어 있지 않습니다.
해결 방법: 계산을 수행하기 전이나 사용자에게 표시하기 전에 이러한 문자열 값을 부동 소수점 숫자로 구문 분석합니다.
Pitfall #26: 엔드포인트별 결합되지 않은 결과
증상: Deals 엔드포인트는 영화와 시즌 모두에 대한 딜을 반환하지 않습니다. itemType 매개변수는 buymovies 또는 seasons만 허용하지만 둘 다는 허용하지 않습니다.
해결 방법: 두 번의 호출을 수행하고 하나의 결합된 응답으로 병합합니다.
Pitfall #27: Search 결과가 메타데이터를 부분적으로 포함함
증상: Search 결과는 종종 누락된 description 필드를 가지며, 이는 Recommendations 엔드포인트를 통해 검색할 수 있습니다(단, 장르별로 필터링된 경우에만 해당).
해결 방법: Recommendations를 사용하여 설명이 필요한 Search 결과를 보강합니다.
Pitfall #28: 여러 스토어에 대한 Prices 호출이 누락된 데이터를 반환함
증상: Prices 엔드포인트를 여러 스토어에 대해 호출할 때, 일부 스토어는 결과를 반환하지만 다른 스토어는 가격 정보가 없는 항목을 반환합니다.
해결 방법: 가격이 있는 스토어만 표시합니다.
Pitfall #29: Charts 및 Topseller의 영화/시즌 분할이 사용 사례별 다름
증상: Charts 및 Topseller 엔드포인트의 결과는 서로 다른 중첩 구조를 가집니다: Charts는 item.movies를 사용하는 반면, Topseller는 store.movies를 사용합니다. 이는 API 전반의 불일치를 만듭니다.
해결 방법: 각 엔드포인트에 대해 고유한 경로로 응답 구조를 처리합니다.
Pitfall #30: Search와 다른 엔드포인트 간의 이미지 URL 필드 이름이 다름
증상: Search 엔드포인트는 표지 이미지 URL에 coverUrl이라는 필드 이름을 사용하는 반면, DetailData는 imageSmallUrl을 사용합니다. 권장되는 것은 imageSmallUrl입니다(Pitfall #32 참조).
해결 방법: 이미지 URL을 검색할 때는 항상 두 필드 이름을 모두 확인하고, 안정성이 더 높으므로 imageSmallUrl을 선호하며 필요한 경우 coverUrl로 대체합니다.
Pitfall #31: 가격이 오른 항목이 중요한 신호임
사용 사례: "방금 세일에서 제외된 것은?" 또는 "다음에 할인될 가능성이 가장 높은 것은?"
증상: DetailData가 이전 가격(priceBefore)이 현재 가격(price)보다 높은 항목을 발견하면, 실제로는 가격이 인상된 것입니다(세일 종료). 표준 워크플로우는 이러한 항목을 제거/무시하도록 권장할 수 있지만, 이는 중요한 신호입니다. 방금 가격이 인상된 항목은 단기간 내에 다시 할인될 가능성이 가장 높습니다.
해결 방법: priceBefore < price인 항목을 필터링하지 마십시오. 대신, 이를 "다음 할인 대상"으로 표시하고 활성 딜과 함께 보고하십시오. 이를 위한 표준 라벨을 제공합니다:
- 활성 할인 항목의 경우: "현재 할인 중: [제목] - [가격] (기존 $[이전 가격]에서 $[차액] 할인)"
- 방금 가격이 오른 항목의 경우: "방금 세일 종료, 다음 할인 예상: [제목] - 현재 $[가격] (최근 $[이전 가격]에서 인상, 다음 할인 가능성 높음)"
Pitfall #32: DetailData 이미지 URL이 더 안정적임
증상: Search 및 다른 엔드포인트의 coverUrl은 때때로 만료되었거나 액세스할 수 없는 이미지를 가리킵니다. DetailData의 imageSmallUrl, imageMediumUrl, imageLargeUrl은 일반적으로 더 안정적이고 오래 지속됩니다.
해결 방법: 가능할 때마다 Search 검색에서 DetailData로 보강할 때 DetailData의 이미지 URL을 선호합니다.
Pitfall #33: Deals 정렬 옵션에 대한 대소문자 구분 없음
증상: llms.txt는 Sort 매개변수 값을 latestPricechange를 포함한 특정 케이스로 문서화하지만, API는 실제로 대소문자를 구분하지 않습니다. latestPriceChange, LatestPriceChange, LATESTPRICECHANGE 모두 동일하게 작동합니다.
해결 방법: 코드 가독성을 위해 llms.txt의 케이스를 사용하지만, 사용자 오류가 있을 때는 대소문자를 구분하지 않는 처리를 구현합니다.
Pitfall #34: DetailData가 빈 번째/시즌 배열을 반환함
증상: DetailData를 movies itemType으로 호출할 때 응답이 항상 빈 {"results":{"seasons":{...}}}를 포함하는 seasons 키를 반환합니다. 영화를 쿼리할 때는 seasons 키를 완전히 무시하십시오. 쿼리하는 itemType과 일치하는 키만 데이터를 포함합니다.
해결 방법: 요청한 itemType과 일치하는 키를 읽습니다.
워크플로우 레시피
레시피: "오늘의 가격 인하"
action=getDeals, store=itunes, country=us, itemType=buymovies, sort=latestPricechange, limit=50으로 Deals API 호출- 응답
status가"success"인지 확인 - 목록의 각 항목에 대해 DetailData 호출 (아래 DetailData 섹션 참조)
- 각 항목의
priceHdLastChangeDate확인 - 오늘 날짜와 일치하는 항목만 "오늘의 할인"으로 적격 - 가짜 할인 제외:
priceBefore - price <= 1인 항목은 진정한 할인이 아닌 노이즈로 간주하여 제외 priceHdIsLowest로 ATL 플래그 보강- 결과 반환
Python 구현 (8개 동시 DetailData 워커):
import asyncio
import aiohttp
from datetime import datetime, date
BEARER_TOKEN = "..." # Pitfall #17 참조
DEALS_URL = "https://buster.cheapcharts.de/v1/gptapi/Deals.php"
DETAIL_URL = "https://buster.cheapcharts.de/v1/DetailData.php"
async def get_detail_data(session, item):
async with session.get(
DETAIL_URL,
params={
"store": item["store"],
"country": item["country"],
"itemType": "movies" if item["mediaType"] == "movie" else "seasons",
"idInStore": item.get("idInStore")
},
headers={"Cookie": f"PHPSESSID={BEARER_TOKEN}"}
) as resp:
data = await resp.json()
results = data.get("results", {})
key = "movies" if item["mediaType"] == "movie" else "seasons"
detail_items = results.get(key, [])
return detail_items[0] if detail_items else {}
async def get_todays_drops():
async with aiohttp.ClientSession() as session:
async with session.get(
DEALS_URL,
params={
"action": "getDeals",
"store": "itunes",
"country": "us",
"itemType": "buymovies",
"sort": "latestPricechange",
"limit": 50
}
) as resp:
deals_data = await resp.json()
if deals_data.get("status") != "success":
return []
items = deals_data.get("results", {}).get("buymovies", [])
sem = asyncio.Semaphore(8)
async def bounded_detail(item):
async with sem:
return await get_detail_data(session, item)
details = await asyncio.gather(*[bounded_detail(item) for item in items])
today = date.today().isoformat()
todays_drops = []
for item, detail in zip(items, details):
change_date = detail.get("priceHdLastChangeDate", "")
if change_date != today:
continue
savings = float(item.get("priceBefore", 0)) - float(item.get("price", 0))
if savings <= 1.0:
continue
item["isATL"] = detail.get("priceHdIsLowest", 0) == 1
todays_drops.append(item)
return todays_drops
레시피: "역대 최저가(ATL) 딜"
사용자가 역대 최저가, 최저가 딜, "역대 가장 싼"을 요청할 때:
action=getDeals, sort=greatestSavings, limit=50, maxPrice=10으로 Deals API 호출- 응답 상태가 성공인지 확인
- 이전 레시피와 동일한 패턴으로 각 항목에 대해 DetailData 호출
priceHdIsLowest == 1인 항목만 유지- 필요에 따라 정렬 및 표시
레시피: "여러 스토어 가격 비교"
- 사용자가 제목을 언급할 때 먼저
action=search로 Search API 호출 - 결과에서
imdbId추출 - 각 관심 스토어에 대해 Prices API 호출: 각 스토어가 가격을 반환하는지 확인하고, 결과를 표시할 때는
isLowest또는 기타 내역 신호와 같은 DetailData 보강을 위해 선택적으로 DetailData 호출 - 가격, 스토어 이름 및 할인율을 포함하여 비교 표시
레시피: "번들 딜 찾기"
action=getDeals, store=itunes, country=us, itemType=seasons, sort=greatestSavings, limit=30으로 Deals API 호출- 응답이 성공하는지 확인
- DetailData 호출로 각 결과 보강
isBundle == 1인 항목만 클라이언트 측에서 필터링bundleSavingsHd또는saveOpportunityHd가 포함된 번들 세부 정보 표시
레시피: "높은 IMDB 평점을 받은 최고의 딜"
action=getDeals, store=itunes, country=us, itemType=buymovies, sort=greatestSavings, maxPrice=15, imdbRating=7, limit=20으로 Deals API 호출- 응답 상태가 성공인지 확인
- DetailData로 보강하고
priceHdIsLowest표시 - 높은 평점, 할인율 및 ATL 상태를 강조하여 결과 표시
레시피: "Charts 데이터 사용"
- 적절한 매개변수와 함께
action=getCharts로 Charts API 호출 - 응답 상태가 성공인지 확인
rank필드로 차트 항목 표시- 각 항목에서
imdbId를 사용하여 DetailData로 선택적으로 보강
프레젠테이션 가이드라인
사용자에게 결과를 표시할 때는 이 구조를 따르세요:
단일 항목 템플릿
[제목] ([출시연도]) - [스토어명] 💰 현재 가격: $[가격] (기존: $[이전가격], [할인율]% 할인) 📉 저장: $[저장액] ⭐ IMDb: [평점]/10 | 🍅 Rotten Tomatoes: [점수]% 🏆 역대 최저가: [예/아니오] [썸네일] CheapCharts 링크
여러 항목 템플릿
검색 결과: [제목]에 대해 [N]개의 딜을 찾았습니다
# 제목 가격 이전 가격 할인 스토어 IMDb ATL 1 [제목] $9.99 $14.99 33% iTunes 8.5 ✅ 2 [제목] $7.99 $12.99 38% Amazon 7.2 ❌ 3 [제목] $4.99 $9.99 50% Vudu 6.8 ✅
할인율 계산: ((float(priceBefore) - float(price)) / float(priceBefore)) * 100
스토어 이름 매핑:
itunes→ "Apple TV" (Pitfall #19 참조)amazon→ "Amazon Prime Video"vudu→ "Vudu"googlePlay→ "Google Play"
저장액 강조 규칙:
- 저장액
> $10→ 초록색 이모지 🟢로 표시 - 저장액
$5 - $10→ 노란색 이모지 🟡로 표시 - 저장액
< $5→ 빨간색 이모지 🔴로 표시
자주 묻는 질문 (FAQ)
Q: 이 API는 무료인가요?
A: 네! CheapCharts GPT API는 완전히 무료이며 공개되어 있습니다. 인증이나 API 키가 필요하지 않습니다.
Q: 속도 제한이 있나요?
A: 공식 문서에 따르면 없습니다. 하지만 과도한 호출을 피하기 위해 합리적인 속도 제한을 구현하는 것이 좋습니다.
Q: 여러 스토어를 동시에 검색할 수 있나요?
A: 네! 예를 들어 store=itunes,amazon,vudu,googlePlay와 같이 쉼표로 구분된 값을 사용하세요. 단, 일부 엔드포인트는 특정 스토어를 지원하지 않을 수 있습니다(예: Amazon 및 Vudu는 Google Play 또는 iTunes에 비해 지원되는 국가가 더 적음).
Q: 어떻게 봉쇄를 피할 수 있나요?
A: CheapCharts는 제한을 강제하지 않지만, 합리적인 사용을 권장합니다. 필요할 때 병렬 요청(예: 8개의 동시 DetailData 워커)은 안전합니다. 연속적인 대규모 배치 요청은 피하세요.
Q: 역대 최저가를 어떻게 확인하나요?
A: priceHdIsLowest 플래그(현재 HD 가격이 이전에 기록된 적 없는 최저 가격인지 나타냄)에 대해 DetailData 응답을 확인하세요. DetailData 엔드포인트를 통해 각 타이틀에 대해 하나의 추가 호출만 필요합니다.
Q: has4K=1 서버 측 필터가 작동하지 않는 이유는 무엇인가요?
A: 이는 API 제한 사항으로, Deals 엔드포인트는 has4K에 대한 서버 측 필터링을 무시합니다. 4K 타이틀을 필터링하려면 전체 데이터를 가져와 클라이언트 측에서 필터링하세요.
Q: 일부 항목에 IMDb ID가 없는 이유는 무엇인가요?
A: 모든 타이틀이 CheapCharts 데이터베이스에 IMDb ID를 가지고 있는 것은 아닙니다. 특히 오래되었거나 덜 알려진 항목의 경우 그렇습니다. 이는 정상적인 동작입니다. IMDb ID가 없는 항목은 표시하기 전에 선택적으로 필터링할 수 있습니다.