{"openapi":"3.1.0","info":{"title":"goodrecmovies public API","version":"1.0.0","description":"Read-only access to the goodrecmovies ranking (every movie above 7.0, ranked by math, not vibes). Versioning policy: the current version is served at /api/*; breaking changes would move to /api/v2/* with this spec updated first — no breaking change ships in place. All operations are unauthenticated: there are no API keys and no OAuth; see /auth.md. Rate limits are per IP and surfaced in the RateLimit-Policy response header; 429 responses carry Retry-After.","contact":{"name":"goodrecmovies","email":"hello@goodrecmovies.com","url":"https://goodrecmovies.com/contact"},"license":{"name":"Data: IMDb public datasets + TMDB","url":"https://www.themoviedb.org"}},"servers":[{"url":"https://goodrecmovies.com","description":"v1 — current stable. Versioning policy: paths are /api/*; breaking changes move to /api/v2/* with this spec updated first; a deprecated version returns Deprecation + Sunset headers for at least 90 days before removal."}],"security":[],"x-versioning-policy":{"current":"v1","transport":"path-prefix on breaking change only","deprecationWindowDays":90},"tags":[{"name":"ranking","description":"The ranked pools"},{"name":"search","description":"Title and people search"},{"name":"ask","description":"Natural-language answers (NLWeb)"}],"paths":{"/api/list":{"get":{"tags":["ranking"],"operationId":"listTitles","summary":"Ranked titles, best first","description":"Paginated ranked pool. Pagination: `page` is 0-based, `pageSize` one of 10/20/50/100; the response carries total, poolN and page so clients can walk the set. Filters (q, kind) narrow the pool before ranking.","parameters":[{"name":"kind","in":"query","schema":{"type":"string","enum":["movie","series"]},"description":"Pool to rank. Default movie."},{"name":"q","in":"query","schema":{"type":"string","maxLength":120},"description":"Title substring filter."},{"name":"page","in":"query","schema":{"type":"integer","minimum":0,"default":0}},{"name":"pageSize","in":"query","schema":{"type":"integer","enum":[10,20,50,100],"default":100}}],"responses":{"200":{"description":"Ranked page","headers":{"RateLimit-Policy":{"schema":{"type":"string"},"description":"e.g. 60;w=60 — per-IP policy"}},"content":{"application/json":{"schema":{"type":"object","properties":{"rows":{"type":"array","items":{"type":"object","properties":{"tconst":{"type":"string","description":"IMDb id, e.g. tt0111161"},"kind":{"type":"string","enum":["movie","series"]},"primaryTitle":{"type":"string"},"startYear":{"type":"integer","nullable":true},"runtimeMin":{"type":"integer","nullable":true},"genres":{"type":"array","items":{"type":"string"}},"votes":{"type":"integer","nullable":true},"goodrecIndex":{"type":"number","description":"Raw ranking value; the site displays GRM = value/2 + 50"},"tier":{"type":"string","enum":["solid","great","excellent","essential"]},"rank":{"type":"integer","description":"Position within its kind pool (1 = best)"},"overview":{"type":"string","nullable":true}}}},"total":{"type":"integer","description":"Rows matching the filters"},"poolN":{"type":"integer","description":"Full pool size for the kind"},"page":{"type":"integer"},"pageSize":{"type":"integer"}}}}}},"400":{"description":"Invalid query params","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","headers":{"Retry-After":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/search":{"get":{"tags":["search"],"operationId":"searchTitles","summary":"Fuzzy title and people search","description":"Accent- and typo-tolerant search across both pools plus cast/director hits. Unauthenticated, rate limited to 30 queries per IP per minute (RateLimit-Policy).","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","maxLength":120}}],"responses":{"200":{"description":"Matches (empty q returns trending)","headers":{"RateLimit-Policy":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited; see Retry-After","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/movies/batch":{"post":{"tags":["ranking"],"operationId":"getTitlesBatch","summary":"Fetch up to 100 titles by IMDb id","description":"Batch lookup. Idempotency: this is a pure read (no side effects), so retries are safe without an Idempotency-Key — the API defines no write operations.","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","maxLength":128},"required":false,"description":"Accepted on every POST for client retry safety. The API defines no write operations — every operation is a pure read and already idempotent — so the key is honored, not recorded."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","items":{"type":"string","pattern":"^tt\\d+$"},"maxItems":5000}},"required":["ids"]}}}},"responses":{"200":{"description":"Found titles","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"Invalid body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/ask":{"post":{"tags":["ask"],"operationId":"ask","summary":"Natural-language question over the ranking (NLWeb)","description":"Accepts {\"query\": \"...\", \"prefer\": {\"streaming\": true}}. Returns NLWeb-shaped JSON (_meta.response_type + version) or text/event-stream with start/result/complete events.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":200},"prefer":{"type":"object","properties":{"streaming":{"type":"boolean"}}}},"required":["query"]}}}},"responses":{"200":{"description":"NLWeb answer (JSON) or SSE stream","content":{"application/json":{"schema":{"type":"object"}},"text/event-stream":{"schema":{"type":"string"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","examples":["bad_request","rate_limited"]},"message":{"type":"string"}},"required":["code","message"]}},"required":["error"]}}}}