{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"SwipeForMovie Catalog API","version":"2.0.0","summary":"Movie and series catalog data for discovery experiences.","description":"Authenticated REST access to the normalized SwipeForMovie movie and series catalog. This contract is generated from the Cloudflare Worker implementation in `scraper for omdb/src`.","contact":{"name":"SwipeForMovie","url":"https://swipeformovie.com"}},"servers":[{"url":"https://api.swipeformovie.com","description":"Production"},{"url":"http://localhost:8787","description":"Local Wrangler development"}],"tags":[{"name":"Catalog","description":"Search, browse, and retrieve normalized titles."},{"name":"System","description":"Public service health."}],"paths":{"/health":{"get":{"tags":["System"],"operationId":"get_health","summary":"Check API and catalog health","description":"Returns service status and catalog import metadata. No API key is required.","security":[],"responses":{"200":{"description":"The service and catalog query are healthy.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/health_response"},"example":{"data":[{"status":"ok","environment":"production","catalog":{"catalog_version":"2.0.0","title_count":200000,"ingested_at":"2026-08-01T00:00:00.000Z","enrichment_complete":true}}],"meta":{"request_id":"req_01J7EXAMPLE"}}}}},"500":{"$ref":"#/components/responses/internal_error"}}}},"/v1/search":{"get":{"tags":["Catalog"],"operationId":"search_titles","summary":"Search titles","description":"Performs normalized full-text search, then orders matches by text relevance, vote count, and title ID. Search input is normalized with Unicode NFKC, control characters are removed, and at most 20 letter-or-number tokens are used.","security":[{"bearer_auth":[]}],"parameters":[{"name":"q","in":"query","required":true,"description":"Search text. After normalization it must contain at least one letter or number.","schema":{"type":"string","minLength":1,"maxLength":100},"example":"matrix"},{"name":"type","in":"query","required":false,"description":"Exact normalized title type.","schema":{"$ref":"#/components/schemas/title_type"},"example":"movie"},{"name":"year","in":"query","required":false,"description":"Exact start year.","schema":{"type":"integer","minimum":1800,"maximum":2100},"example":1999},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of matches. An empty search returns `data: []` with `total_results: 0` and `total_pages: 0`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/catalog_response"},"examples":{"search":{"$ref":"#/components/examples/search_response"}}}}},"400":{"$ref":"#/components/responses/validation_error"},"401":{"$ref":"#/components/responses/authentication_error"},"403":{"$ref":"#/components/responses/disabled_key_error"},"429":{"$ref":"#/components/responses/quota_error"},"500":{"$ref":"#/components/responses/internal_error_rate_limited"}}}},"/v1/titles/{imdb_id}":{"get":{"tags":["Catalog"],"operationId":"get_title","summary":"Get a title","description":"Retrieves one normalized title by its catalog IMDb identifier. A successful response always contains exactly one item in `data`.","security":[{"bearer_auth":[]}],"parameters":[{"name":"imdb_id","in":"path","required":true,"description":"A title identifier beginning with `tt` followed by one or more digits.","schema":{"type":"string","pattern":"^tt\\d+$"},"example":"tt0133093"}],"responses":{"200":{"description":"The title was found.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/title_response"},"examples":{"title":{"$ref":"#/components/examples/title_response"}}}}},"400":{"$ref":"#/components/responses/validation_error_id"},"401":{"$ref":"#/components/responses/authentication_error"},"403":{"$ref":"#/components/responses/disabled_key_error"},"404":{"$ref":"#/components/responses/title_not_found"},"429":{"$ref":"#/components/responses/quota_error"},"500":{"$ref":"#/components/responses/internal_error_rate_limited"}}}},"/v1/movies":{"get":{"tags":["Catalog"],"operationId":"list_movies","summary":"List movies","description":"Lists stored `movie` and `tv_movie` titles ordered by vote count descending, then title ID ascending.","security":[{"bearer_auth":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/catalog_page"},"400":{"$ref":"#/components/responses/validation_error"},"401":{"$ref":"#/components/responses/authentication_error"},"403":{"$ref":"#/components/responses/disabled_key_error"},"429":{"$ref":"#/components/responses/quota_error"},"500":{"$ref":"#/components/responses/internal_error_rate_limited"}}}},"/v1/series":{"get":{"tags":["Catalog"],"operationId":"list_series","summary":"List series","description":"Lists stored `tv_series`, `tv_mini_series`, and `tv_special` titles ordered by vote count descending, then title ID ascending.","security":[{"bearer_auth":[]}],"parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"$ref":"#/components/responses/catalog_page"},"400":{"$ref":"#/components/responses/validation_error"},"401":{"$ref":"#/components/responses/authentication_error"},"403":{"$ref":"#/components/responses/disabled_key_error"},"429":{"$ref":"#/components/responses/quota_error"},"500":{"$ref":"#/components/responses/internal_error_rate_limited"}}}}},"components":{"securitySchemes":{"bearer_auth":{"type":"http","scheme":"bearer","bearerFormat":"mov_live_...","description":"Send the complete generated API key in `Authorization: Bearer <key>`. Valid key strings begin with `mov_live_` and contain at least 43 URL-safe base64 characters after the prefix."}},"parameters":{"page":{"name":"page","in":"query","required":false,"description":"One-based page number. Pages beyond the result set return an empty `data` array.","schema":{"type":"integer","minimum":1,"maximum":1000000,"default":1},"example":1},"limit":{"name":"limit","in":"query","required":false,"description":"Results per page.","schema":{"type":"integer","minimum":1,"maximum":20,"default":20},"example":20}},"headers":{"request_id":{"description":"The accepted client request ID or a generated UUID. A supplied `X-Request-Id` is accepted only when it is at most 128 characters and contains letters, digits, `.`, `_`, or `-`.","schema":{"type":"string"},"example":"req_01J7EXAMPLE"},"rate_limit_limit":{"description":"The authenticated key's configured requests-per-minute policy.","schema":{"type":"integer"},"example":60},"rate_limit_remaining":{"description":"Best-effort remaining value. The Cloudflare binding exposes allow or deny, not an exact counter; accepted requests currently return `limit - 1` and rejected requests return `0`.","schema":{"type":"integer"},"example":59},"retry_after":{"description":"Seconds until the caller should retry. Minute throttles return `60`; exhausted monthly quotas return seconds until the first day of the next UTC month.","schema":{"type":"integer","minimum":1},"example":60}},"schemas":{"title_type":{"type":"string","enum":["movie","tv_movie","tv_series","tv_mini_series","tv_special"]},"request_meta":{"type":"object","required":["request_id"],"properties":{"request_id":{"type":"string"}},"additionalProperties":false},"page_meta":{"type":"object","required":["request_id","page","per_page","total_results","total_pages"],"properties":{"request_id":{"type":"string"},"page":{"type":"integer","minimum":1,"maximum":1000000},"per_page":{"type":"integer","minimum":1,"maximum":20},"total_results":{"type":"integer","minimum":0},"total_pages":{"type":"integer","minimum":0}},"additionalProperties":false},"ratings":{"type":"object","required":["average","votes"],"properties":{"average":{"type":"number","minimum":0,"maximum":10},"votes":{"type":"integer","minimum":0}},"additionalProperties":false},"title":{"type":"object","required":["id","imdb_id","type","title","original_title","year","end_year","runtime_minutes","genres","content_rating","plot","languages","countries","directors","writers","actors","awards","poster_url","box_office","production","total_seasons","ratings"],"properties":{"id":{"type":"integer","description":"Internal numeric catalog ID."},"imdb_id":{"type":"string","pattern":"^tt\\d+$","description":"Stable title identifier exposed by the catalog."},"type":{"$ref":"#/components/schemas/title_type"},"title":{"type":"string"},"original_title":{"type":["string","null"]},"year":{"type":["integer","null"]},"end_year":{"type":["integer","null"]},"runtime_minutes":{"type":["integer","null"]},"genres":{"type":["array","null"],"items":{"type":"string"}},"content_rating":{"type":["string","null"]},"plot":{"type":["string","null"]},"languages":{"type":["array","null"],"items":{"type":"string"}},"countries":{"type":["array","null"],"items":{"type":"string"}},"directors":{"type":["array","null"],"items":{"type":"string"}},"writers":{"type":["array","null"],"items":{"type":"string"}},"actors":{"type":["array","null"],"items":{"type":"string"}},"awards":{"type":["string","null"]},"poster_url":{"type":["string","null"],"format":"uri"},"box_office":{"type":["string","null"]},"production":{"type":["string","null"]},"total_seasons":{"type":["integer","null"],"minimum":1},"ratings":{"$ref":"#/components/schemas/ratings"}},"additionalProperties":false},"catalog_response":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/title"}},"meta":{"$ref":"#/components/schemas/page_meta"}},"additionalProperties":false},"title_response":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","minItems":1,"maxItems":1,"items":{"$ref":"#/components/schemas/title"}},"meta":{"$ref":"#/components/schemas/request_meta"}},"additionalProperties":false},"health_response":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"type":"object","required":["status","environment","catalog"],"properties":{"status":{"const":"ok"},"environment":{"type":"string"},"catalog":{"type":"object","required":["catalog_version","title_count","ingested_at","enrichment_complete"],"properties":{"catalog_version":{"type":["string","null"]},"title_count":{"type":"integer","minimum":0},"ingested_at":{"type":["string","null"]},"enrichment_complete":{"type":"boolean"}},"additionalProperties":false}},"additionalProperties":false}},"meta":{"$ref":"#/components/schemas/request_meta"}},"additionalProperties":false},"error_code":{"type":"string","enum":["VALIDATION_ERROR","MISSING_API_KEY","INVALID_API_KEY","API_KEY_DISABLED","TITLE_NOT_FOUND","RATE_LIMITED","MONTHLY_QUOTA_EXCEEDED","ROUTE_NOT_FOUND","INTERNAL_ERROR"]},"error":{"type":"object","required":["code","message","request_id"],"properties":{"code":{"$ref":"#/components/schemas/error_code"},"message":{"type":"string"},"request_id":{"type":"string"}},"additionalProperties":false},"error_response":{"type":"object","required":["error"],"properties":{"error":{"$ref":"#/components/schemas/error"}},"additionalProperties":false}},"examples":{"title_response":{"value":{"data":[{"id":1,"imdb_id":"tt0133093","type":"movie","title":"The Matrix","original_title":"The Matrix","year":1999,"end_year":null,"runtime_minutes":136,"genres":["Action","Sci-Fi"],"content_rating":"R","plot":"A computer hacker learns that the world he knows is a simulated reality.","languages":["English"],"countries":["United States"],"directors":["Lana Wachowski","Lilly Wachowski"],"writers":["Lilly Wachowski","Lana Wachowski"],"actors":["Keanu Reeves","Laurence Fishburne","Carrie-Anne Moss"],"awards":"Won 4 Academy Awards.","poster_url":"https://images.example.com/poster.jpg","box_office":"$172,076,928","production":null,"total_seasons":null,"ratings":{"average":8.7,"votes":2200000}}],"meta":{"request_id":"req_01J7EXAMPLE"}}},"search_response":{"value":{"data":[{"id":1,"imdb_id":"tt0133093","type":"movie","title":"The Matrix","original_title":"The Matrix","year":1999,"end_year":null,"runtime_minutes":136,"genres":["Action","Sci-Fi"],"content_rating":"R","plot":"A computer hacker learns that the world he knows is a simulated reality.","languages":["English"],"countries":["United States"],"directors":["Lana Wachowski","Lilly Wachowski"],"writers":["Lilly Wachowski","Lana Wachowski"],"actors":["Keanu Reeves","Laurence Fishburne","Carrie-Anne Moss"],"awards":"Won 4 Academy Awards.","poster_url":"https://images.example.com/poster.jpg","box_office":"$172,076,928","production":null,"total_seasons":null,"ratings":{"average":8.7,"votes":2200000}}],"meta":{"request_id":"req_01J7EXAMPLE","page":1,"per_page":20,"total_results":2,"total_pages":1}}},"validation_error":{"value":{"error":{"code":"VALIDATION_ERROR","message":"The request parameters are invalid.","request_id":"req_01J7EXAMPLE"}}},"missing_key":{"value":{"error":{"code":"MISSING_API_KEY","message":"An API key is required.","request_id":"req_01J7EXAMPLE"}}},"invalid_key":{"value":{"error":{"code":"INVALID_API_KEY","message":"The supplied API key is invalid.","request_id":"req_01J7EXAMPLE"}}},"disabled_key":{"value":{"error":{"code":"API_KEY_DISABLED","message":"This API key has been disabled.","request_id":"req_01J7EXAMPLE"}}},"rate_limited":{"value":{"error":{"code":"RATE_LIMITED","message":"Too many requests. Please retry later.","request_id":"req_01J7EXAMPLE"}}},"monthly_quota":{"value":{"error":{"code":"MONTHLY_QUOTA_EXCEEDED","message":"The monthly API quota has been exceeded.","request_id":"req_01J7EXAMPLE"}}}},"responses":{"catalog_page":{"description":"A page ordered by vote count descending, then title ID ascending.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/catalog_response"},"examples":{"catalog":{"$ref":"#/components/examples/search_response"}}}}},"validation_error":{"description":"A query is missing, unknown, malformed, or outside its allowed range.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"examples":{"validation":{"$ref":"#/components/examples/validation_error"}}}}},"validation_error_id":{"description":"The title ID does not match `^tt\\d+$`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"example":{"error":{"code":"VALIDATION_ERROR","message":"The IMDb ID must match ^tt\\d+$.","request_id":"req_01J7EXAMPLE"}}}}},"authentication_error":{"description":"The API key is missing or invalid. The response is `MISSING_API_KEY` or `INVALID_API_KEY`.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"examples":{"missing":{"$ref":"#/components/examples/missing_key"},"invalid":{"$ref":"#/components/examples/invalid_key"}}}}},"disabled_key_error":{"description":"The key exists but has been disabled.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"examples":{"disabled":{"$ref":"#/components/examples/disabled_key"}}}}},"title_not_found":{"description":"No title has the supplied identifier.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"example":{"error":{"code":"TITLE_NOT_FOUND","message":"The requested title was not found.","request_id":"req_01J7EXAMPLE"}}}}},"quota_error":{"description":"The key exceeded either its minute rate limit (`RATE_LIMITED`) or monthly quota (`MONTHLY_QUOTA_EXCEEDED`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"Retry-After":{"$ref":"#/components/headers/retry_after"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"examples":{"minute":{"$ref":"#/components/examples/rate_limited"},"monthly":{"$ref":"#/components/examples/monthly_quota"}}}}},"internal_error":{"description":"An internal error occurred. Implementation details are not exposed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"example":{"error":{"code":"INTERNAL_ERROR","message":"An internal error occurred.","request_id":"req_01J7EXAMPLE"}}}}},"internal_error_rate_limited":{"description":"An internal error occurred after authentication and minute-limit evaluation. Implementation details are not exposed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/request_id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/rate_limit_limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/rate_limit_remaining"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/error_response"},"example":{"error":{"code":"INTERNAL_ERROR","message":"An internal error occurred.","request_id":"req_01J7EXAMPLE"}}}}}}}}