{"openapi":"3.1.0","info":{"title":"RetouchEngine","description":"Deterministic, non-generative photo adjustments as an API. Every operation is documented pixel math — the same call always returns the same image, and no AI ever invents content. Authenticate with the X-API-Key header. Full op catalog: GET /v1/ops.","version":"1.0.0"},"paths":{"/v1/ops":{"get":{"summary":"Catalog","description":"Machine-readable catalog: every operation, parameter, range and default.","operationId":"catalog_v1_ops_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/edit":{"post":{"summary":"Edit","description":"Run a chain of adjustments on one image. Output: image/jpeg or image/png.\n\nResponse headers: X-Ops-Applied (JSON log), X-Elapsed-Ms, X-Output-Size.\nAll EXIF is stripped from the output (privacy); orientation is baked in first.","operationId":"edit_v1_edit_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_edit_v1_edit_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/analyze":{"post":{"summary":"Analyze Photo","description":"Read with AI: the model LOOKS at the photo and recommends catalog settings\nfor your style brief — it never touches a pixel. Default response is JSON\n{\"chain\": [...], \"notes\": \"...\"} you can pass straight to /v1/edit (where the\nedit itself is fully deterministic). With apply=true the chain runs at once\nand you get the JPEG back, with X-Ops-Applied showing exactly what ran.","operationId":"analyze_photo_v1_analyze_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_analyze_photo_v1_analyze_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/presets":{"get":{"summary":"Presets List","description":"Your saved presets (named chains). Use their names in /v1/edit's preset field.","operationId":"presets_list_v1_presets_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Presets Save","description":"Create/replace a preset: {\"name\": \"my_look\", \"chain\": [{\"op\": ...}, ...]}.\nChains may reference your custom ops (\"custom:name\").","operationId":"presets_save_v1_presets_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/presets/{name}":{"delete":{"summary":"Presets Delete","description":"Delete one of your presets.","operationId":"presets_delete_v1_presets__name__delete","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/custom-ops":{"get":{"summary":"Custom List","description":"Your AI-generated operations, with every version and its request.","operationId":"custom_list_v1_custom_ops_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Custom Create","description":"Create an operation from natural language: {\"name\": \"beard_glow\",\n\"request\": \"gently brighten beard areas on faces\"}. The AI writes the code,\nthe runtime gates it (AST allow-list, sandboxed test run, determinism check)\nand stores it as version 1. Use it in /v1/edit as {\"op\": \"custom:beard_glow\"}.","operationId":"custom_create_v1_custom_ops_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/custom-ops/{name}/versions":{"post":{"summary":"Custom Iterate","description":"Iterate on an operation: {\"request\": \"softer edges, stronger effect\"}.\nThe AI updates the CURRENT code into a NEW version; every old version stays\ncallable via {\"op\": \"custom:name\", \"v\": N}.","operationId":"custom_iterate_v1_custom_ops__name__versions_post","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/custom-ops/{name}/code":{"get":{"summary":"Custom Code","description":"Read the code of a version (default: latest).","operationId":"custom_code_v1_custom_ops__name__code_get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}},{"name":"v","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"V"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_analyze_photo_v1_analyze_post":{"properties":{"image":{"type":"string","format":"binary","title":"Image","description":"JPEG/PNG/WebP — the photo the AI should READ"},"prompt":{"type":"string","title":"Prompt","description":"Style brief, e.g. \"candid style editing with a bit of glamour\"","default":""},"model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model","description":"Analyze model — default gpt-5-mini (small & fast); set e.g. gpt-5 for a bigger read"},"apply":{"type":"boolean","title":"Apply","description":"true: run the recommended chain immediately and return the JPEG","default":false},"quality":{"type":"integer","maximum":100.0,"minimum":50.0,"title":"Quality","default":92},"max_edge":{"anyOf":[{"type":"integer","maximum":8000.0,"minimum":200.0},{"type":"null"}],"title":"Max Edge"}},"type":"object","required":["image"],"title":"Body_analyze_photo_v1_analyze_post"},"Body_edit_v1_edit_post":{"properties":{"image":{"type":"string","format":"binary","title":"Image","description":"JPEG/PNG/WebP, up to 40 MB / 40 MP"},"ops":{"type":"string","title":"Ops","description":"JSON list, e.g. [{\"op\":\"autotone\"},{\"op\":\"clarity\",\"amount\":0.5}]","default":"[]"},"preset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Preset","description":"Named preset (see /v1/ops) — runs BEFORE ops"},"logo":{"anyOf":[{"type":"string","format":"binary"},{"type":"null"}],"title":"Logo","description":"PNG with transparency for the \"logo\" op"},"quality":{"type":"integer","maximum":100.0,"minimum":50.0,"title":"Quality","description":"JPEG quality of the output","default":92},"max_edge":{"anyOf":[{"type":"integer","maximum":8000.0,"minimum":200.0},{"type":"null"}],"title":"Max Edge","description":"Downscale the long edge (optional)"},"fmt":{"type":"string","title":"Fmt","description":"Output format: \"jpeg\" or \"png\" (png keeps a cutout transparent)","default":"jpeg"}},"type":"object","required":["image"],"title":"Body_edit_v1_edit_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}