{"openapi":"3.1.0","info":{"title":"FrameThrower API","version":"1.0.0","summary":"Cinematography reference search over 5,489 films.","description":"Search a film stills library indexed frame by frame on lighting, lens character, shot size, camera angle, colour palette and mood — in plain language, by reference image, or by colour.\n\nResponses carry metadata, thumbnail URLs, colour palettes and deep links. **Never raw image bytes** — this is a reference and discovery API, not an image-delivery pipe. The frames are not ours to license and rights remain with their owners.\n\n**Pricing:** $2 of credits free on signup, no card and no plan required. Then $1 = 1,000 credits; a search costs 2, so $1 buys 500 searches. Credits never expire and there are no rate limits. Every metered response carries `X-Credits-Cost` and `X-Credits-Remaining`.","contact":{"name":"FrameThrower","url":"https://framethrower.ai/developers","email":"contact@framethrower.ai"},"license":{"name":"Proprietary","url":"https://framethrower.ai/legal/tos"},"termsOfService":"https://framethrower.ai/legal/tos"},"servers":[{"url":"https://framethrower.ai/api/v1","description":"Production"}],"externalDocs":{"url":"https://framethrower.ai/developers","description":"Developer documentation"},"security":[{"bearerAuth":[]}],"tags":[{"name":"Search","description":"Find frames by text, image or colour"},{"name":"Frames","description":"Individual frames and their metadata"},{"name":"Films","description":"The film catalogue"},{"name":"Discovery","description":"Autocomplete and helpers"}],"paths":{"/search":{"post":{"tags":["Search"],"operationId":"search","summary":"Search by text","description":"Describe a mood, scene or visual concept in natural language.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"Natural-language query","examples":["neon-lit rainy street at night"]},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20},"mode":{"type":"string","enum":["hybrid","description"],"default":"hybrid","description":"hybrid fuses the image and scene-description embeddings via reciprocal rank fusion — use it unless you have a reason not to. description ranks on the written descriptions only, better when the query is about story, action or named objects."},"color":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$","examples":["#1f7a8c"],"description":"Bias the search toward frames dominated by this colour, without abandoning the query. Fused with the text ranking rather than replacing it — \"ocean\" with #1f7a8c returns teal seas, not teal anything. For colour alone, use /search/color."},"enhance":{"type":"boolean","default":false,"description":"Rewrite the query into the descriptive register the frames were embedded from before searching — \"man on fire\" becomes a full sentence. Benchmarked recall@10: 0.72 raw, 0.88 enhanced. This is what the website does by default. Adds ~1s on a cache miss and falls back to the raw query on error. When it fires, the rewritten text comes back as meta.enhancedQuery — send that as the query for later pages so every page ranks in the same neighbourhood."},"offset":{"type":"integer","minimum":0,"maximum":500,"default":0,"description":"Skip this many results before returning. Use with limit to page: offset 0 then 50 then 100. Capped at 500 — ranking past that is noise, and the candidate pool is built to offset+limit."},"shot_type":{"type":"string","enum":["medium","closeup","wide","fullbody","establishing"],"description":"Shot size. Aliased, so close-up and closeup both land."},"camera_angle":{"type":"string","enum":["eye_level","high_angle","low_angle","top_down","overhead","worms_eye","dutch_angle","pov","birds_eye"],"description":"Camera angle."},"time_of_day":{"type":"string","enum":["night","day","interior_indeterminate","golden_hour","dusk","blue_hour","dawn","overcast"],"description":"Time of day / light."},"setting":{"type":"string","enum":["interior","exterior","both"],"description":"Interior or exterior."},"visual_style":{"type":"string","enum":["live_action","cg_stylized","anime","cg_photorealistic","cartoon_2d","stop_motion","mixed_media","rotoscope","noir","watercolor","minimalist","documentary","expressionist","painterly","surreal"],"description":"Medium and treatment."},"lens":{"type":"string","enum":["anamorphic","spherical","vintage_soft"],"description":"Lens character."},"lighting":{"type":"string","enum":["sun","window","moonlight","practical","neon","fluorescent","firelight","candlelight","streetlamp","screen_glow","headlight","lightning"],"description":"What is lighting the frame — a different question from the time of day."},"depth_of_field":{"type":"string","enum":["shallow","deep","rack_focus","medium"],"description":"Focus depth."},"director":{"type":"string"},"genre":{"type":"string"},"film_title":{"type":"string"},"era":{"type":"string","enum":["ancient","medieval","renaissance","1800s","1900s_1910s","1920s","1930s","1940s","1950s","1960s","1970s","1980s","1990s","2000s","2010s","contemporary","near_future","far_future","fantasy"],"description":"The era depicted on screen, not the release year."},"year_min":{"type":"integer"},"year_max":{"type":"integer"}}}}}},"responses":{"200":{"description":"Matching frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/search/image":{"post":{"tags":["Search"],"operationId":"searchByImage","summary":"Search by reference image","description":"Find frames matching a reference image by visual similarity, colour and semantic meaning.\n\n**Cost: 10 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["imageUrl"],"properties":{"imageUrl":{"type":"string","format":"uri"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20}}}}}},"responses":{"200":{"description":"Matching frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/search/color":{"post":{"tags":["Search"],"operationId":"searchByColor","summary":"Search by colour","description":"Find frames dominated by a hex colour or a blended palette. Use `color` OR `colors`, not both.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"color":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$","examples":["#e94560"]},"colors":{"type":"array","maxItems":6,"items":{"type":"string","pattern":"^#?[0-9a-fA-F]{6}$"}},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20}}}}}},"responses":{"200":{"description":"Matching frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/browse":{"post":{"tags":["Search"],"operationId":"browse","summary":"Filter by craft attribute","description":"Filter frames by cinematography attributes — no text query needed. Combine any filters. Common spellings (\"close-up\", \"magic hour\") are normalised for you; anything unrecognised comes back in `meta.hint` rather than as a silent empty result.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shot_type":{"type":"string","enum":["medium","closeup","wide","fullbody","establishing"],"description":"Shot size"},"lens_character":{"type":"string","enum":["anamorphic","spherical","vintage_soft"],"description":"Lens character"},"setting":{"type":"string","enum":["interior","exterior","both"],"description":"Interior or exterior"},"time_of_day":{"type":"string","enum":["night","day","interior_indeterminate","golden_hour","dusk","blue_hour","dawn","overcast"],"description":"Time of day"},"camera_angle":{"type":"string","enum":["eye_level","high_angle","low_angle","top_down","overhead","worms_eye","dutch_angle","pov","birds_eye"],"description":"Camera angle"},"visual_style":{"type":"string","enum":["live_action","cg_stylized","anime","cg_photorealistic","cartoon_2d","stop_motion","mixed_media","rotoscope","noir","watercolor","minimalist","documentary","expressionist","painterly","surreal"],"description":"Visual style"},"director":{"type":"string","description":"Free text, e.g. \"Ridley Scott\""},"genre":{"type":"string","description":"Free text, e.g. \"Science Fiction\""},"era":{"type":"string","description":"Depicted era, e.g. \"1980s\", \"near_future\""},"year_min":{"type":"integer"},"year_max":{"type":"integer"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20}}}}}},"responses":{"200":{"description":"Matching frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/similar":{"post":{"tags":["Search"],"operationId":"findSimilar","summary":"Find similar frames","description":"More frames like one already returned. Pass several ids to search from their centroid.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"frameId":{"type":"string"},"frameIds":{"type":"array","items":{"type":"string"}},"mode":{"type":"string","enum":["semantic","visual","color"],"default":"semantic"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20}}}}}},"responses":{"200":{"description":"Matching frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/frames":{"get":{"tags":["Frames"],"operationId":"getFrame","summary":"Frame detail","description":"Full metadata, AI image prompt, film info and cinematography attributes for one frame.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The frame","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"No such frame"}}}},"/frames/random":{"get":{"tags":["Frames"],"operationId":"randomFrames","summary":"Random sample","description":"A diverse random sample — useful for inspiration or seeding a UI.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"Frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/suggest":{"get":{"tags":["Discovery"],"operationId":"suggest","summary":"Autocomplete","description":"Fuzzy-match film titles and director/DP names.\n\n**Free — 0 credits.** It is an in-memory prefix match and costs nothing to serve, so charging per keystroke would be hostile.","security":[{"bearerAuth":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}}],"responses":{"200":{"description":"Suggestions","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/films":{"get":{"tags":["Films"],"operationId":"listFilms","summary":"Film catalogue","description":"Paginated catalogue with poster art. Every entry carries `slug`, `frameCount` and a ready-made `framesUrl`.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":50,"maximum":100}},{"name":"q","in":"query","schema":{"type":"string"},"description":"Filter by film title, director or cinematographer"},{"name":"sort","in":"query","schema":{"type":"string","enum":["title","frames","year"],"default":"title"},"description":"frames = best-covered films first"},{"name":"min_frames","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"Films","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}},"/films/{slug}":{"get":{"tags":["Films"],"operationId":"getFilm","summary":"Film detail","description":"Plot, runtime, rating, dominant colours and sample frames.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"examples":{"blade":{"value":"blade-runner-2049-2017"}}}],"responses":{"200":{"description":"The film","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"},"404":{"description":"No such film"}}}},"/films/frames":{"get":{"tags":["Films"],"operationId":"listFilmFrames","summary":"One film's frames","description":"Every frame held for one film, paginated. `meta.total` is the film's full frame count, so page 1 tells you the whole size.\n\n**Cost: 2 credits.** $1 = 1,000 credits. Returns 402 when the balance is short.","security":[{"bearerAuth":[]}],"parameters":[{"name":"slug","in":"query","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":100,"maximum":200}}],"responses":{"200":{"description":"Frames","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Frame"}},"meta":{"type":"object","additionalProperties":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/InsufficientCredits"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Personal access token, prefix `ft_`. Create one at https://framethrower.ai/settings?tab=api — free accounts included, no plan required."}},"schemas":{"Frame":{"type":"object","description":"A single film frame. Thumbnails and deep links only — never raw image bytes.","properties":{"id":{"type":"string"},"url":{"type":"string","format":"uri","description":"Deep link to this frame on FrameThrower"},"thumbnail":{"type":"string","format":"uri"},"score":{"type":"number","description":"Relevance, when the call was a search"},"film":{"type":"object","properties":{"title":{"type":"string"},"year":{"type":"integer"},"slug":{"type":"string"},"frameCount":{"type":"integer"},"director":{"type":"string"},"cinematographer":{"type":"string"}}},"shot_type":{"type":"string"},"lens_character":{"type":"string"},"camera_angle":{"type":"string"},"setting":{"type":"string"},"time_of_day":{"type":"string"},"visual_style":{"type":"string"},"colorPalette":{"type":"array","items":{"type":"string"},"description":"Dominant colours as hex"},"colorDescription":{"type":"string"},"prompt":{"type":"string","description":"Stored descriptive prompt for the frame"}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"docs":{"type":"string"},"api_reference":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, revoked or unknown token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InsufficientCredits":{"description":"Not enough credits. The body carries everything needed to recover without reading docs.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","const":"insufficient_credits"},"cost":{"type":"integer"},"balance":{"type":"integer"},"buy_credits":{"type":"string","format":"uri"}}}}}}}}}