{"openapi":"3.1.0","info":{"title":"Retouch Homes","version":"v1","summary":"Automatic listing-photo editing that never invents pixels.","description":"Retouch Homes (retouch.homes) edits real estate listing photos automatically: it straightens walls, levels horizons, crops, and sets exposure, white balance and sharpness. It never invents pixels: every edit moves or re-weights pixels already in the photo, and nothing in the room is added, removed or changed. Each photo gets a confidence score, and photos below 0.4 are flagged for a human look.\n\nDuring the beta, the API fetches photos only from hosts on its allowlist; a photo on any other host fails with `photo host … is not allowed`. To add yours, write to hello@retouch.homes.\n\nSend photo URLs, get edited photos back in the same response, one entry per photo. Retries are safe: a URL your organization already sent, with the same `long_edge`, `format` and window recovery, is the same photo, so a finished one comes back as it is and isn't counted again. Quickstart: https://retouch.homes/llms-full.txt","termsOfService":"https://retouch.homes/terms","contact":{"name":"Retouch Homes","url":"https://retouch.homes","email":"hello@retouch.homes"}},"servers":[{"url":"https://retouch.homes"}],"security":[{"secretKey":[]}],"tags":[{"name":"Photos","description":"Edit listing photos and read the results."}],"paths":{"/api/v1/photos":{"post":{"operationId":"createPhotos","tags":["Photos"],"summary":"Edit photos","description":"Fetches each photo URL, edits it and returns one photo object per URL, in the order sent. The call is synchronous and can take up to about 200 seconds for a full batch. The request returns 200 even when some photos fail: each failed photo carries its own `error`, and photos left when the time budget runs out fail too, to be sent again. A photo another request is still processing comes back as `processing`; read it later with `GET /api/v1/photos/{id}`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePhotosRequest"},"examples":{"listing":{"summary":"Two photos of one listing, as WebP","value":{"photos":["https://photos.example.com/listing-1042/living-room.jpg","https://photos.example.com/listing-1042/kitchen.jpg"],"listing":"L-1042","long_edge":2048,"format":"webp","market":"malaysia"}}}}}},"responses":{"200":{"description":"One photo object per URL sent, in order. Check each photo's `status`.","content":{"application/json":{"schema":{"type":"object","required":["photos"],"properties":{"photos":{"type":"array","items":{"$ref":"#/components/schemas/Photo"}}}},"examples":{"listing":{"summary":"One photo edited, one failed (report abridged)","value":{"photos":[{"id":"photo_01kfz3m8q2e4t6v8w0y2a4c6e8","object":"photo","status":"succeeded","source_url":"https://photos.example.com/listing-1042/living-room.jpg","listing":"L-1042","output_url":"https://omunafaeewioeslh.public.blob.vercel-storage.com/photos/photo_01kfz3m8q2e4t6v8w0y2a4c6e8/living-room-Xb3kQ9.webp","flagged":false,"confidence":0.86,"report":{"file":"living-room.jpg","input_size":[4032,3024],"output_size":[2048,1536],"distortions_found":{"camera_roll":{"degrees":-1.4,"severity":"moderate"},"vertical_keystone":{"degrees":2.1,"direction":"camera aimed down","severity":"moderate"}},"corrections_applied":{"tilt_correction_deg":2.1,"crop_retained_pct":94.2},"confidence":0.86},"error":null,"created":1794240000},{"id":"photo_01kfz3m8q2e4t6v8w0y2a4c6ea","object":"photo","status":"failed","source_url":"https://photos.example.com/listing-1042/kitchen.jpg","listing":"L-1042","output_url":null,"flagged":null,"confidence":null,"report":null,"error":{"type":"photo_error","code":"photo_failed","message":"photo is larger than 25 MB"},"created":1794240000}]}}}}}},"400":{"description":"The request body is invalid. Codes: `invalid_json`, `parameter_missing`, `too_many_photos`, `parameter_invalid`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_json":{"summary":"The body isn't a JSON object.","value":{"error":{"type":"invalid_request_error","code":"invalid_json","message":"Send a JSON object with a `photos` array of URLs.","param":null,"doc_url":null}}},"parameter_missing":{"summary":"`photos` is missing or empty.","value":{"error":{"type":"invalid_request_error","code":"parameter_missing","message":"`photos` must be an array of one or more photo URLs.","param":"photos","doc_url":null}}},"too_many_photos":{"summary":"More than 40 photos.","value":{"error":{"type":"invalid_request_error","code":"too_many_photos","message":"Send at most 40 photos per request.","param":"photos","doc_url":null}}},"parameter_invalid":{"summary":"A field is malformed; `param` names it, e.g. `photos[2]` or `long_edge`.","value":{"error":{"type":"invalid_request_error","code":"parameter_invalid","message":"Each photo must be an http(s) URL.","param":"photos[0]","doc_url":null}}}}}}},"401":{"description":"The API key is missing or invalid. Codes: `missing_api_key`, `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"summary":"No `Authorization: Bearer` header.","value":{"error":{"type":"authentication_error","code":"missing_api_key","message":"Send your secret key as `Authorization: Bearer rth_sk_live_…`.","param":null,"doc_url":null}}},"invalid_api_key":{"summary":"The key is unknown or revoked.","value":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"The API key is invalid or has been revoked.","param":null,"doc_url":null}}}}}}},"402":{"description":"Your organization can't start these photos. Codes: `insufficient_credits`, `plan_unknown`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"insufficient_credits":{"summary":"Your organization has fewer photos left than the request needs.","value":{"error":{"type":"invalid_request_error","code":"insufficient_credits","message":"You have 0 photos left and these need 2. Buy photos at https://retouch.homes/app/billing.","param":null,"doc_url":null}}},"plan_unknown":{"summary":"Your organization has no active plan.","value":{"error":{"type":"invalid_request_error","code":"plan_unknown","message":"Your organization has no active plan.","param":null,"doc_url":null}}}}}}},"429":{"description":"Too many requests for this key. Codes: `rate_limited`.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"Over 60 requests a minute per key. Wait `Retry-After` seconds.","value":{"error":{"type":"rate_limit_error","code":"rate_limited","message":"Too many requests for this key. Retry in 12 s.","param":null,"doc_url":null}}}}}}},"500":{"description":"An error on our side. Codes: `internal_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"Something failed on our side. Retry.","value":{"error":{"type":"api_error","code":"internal_error","message":"Something went wrong on our side. Retry the request.","param":null,"doc_url":null}}}}}}},"502":{"description":"Photo processing is unavailable. Codes: `engine_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"engine_unavailable":{"summary":"Photo processing is unavailable. Retry.","value":{"error":{"type":"api_error","code":"engine_unavailable","message":"Photo processing is unavailable. Retry the request.","param":null,"doc_url":null}}}}}}}}}},"/api/v1/photos/{id}":{"get":{"operationId":"getPhoto","tags":["Photos"],"summary":"Get a photo","description":"Returns one photo of your organization, in its current state.","parameters":[{"name":"id","in":"path","required":true,"description":"The photo's ID, as `POST /api/v1/photos` returned it.","schema":{"type":"string"},"example":"photo_01kfz3m8q2e4t6v8w0y2a4c6e8"}],"responses":{"200":{"description":"The photo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Photo"},"examples":{"succeeded":{"summary":"An edited photo (report abridged)","value":{"id":"photo_01kfz3m8q2e4t6v8w0y2a4c6e8","object":"photo","status":"succeeded","source_url":"https://photos.example.com/listing-1042/living-room.jpg","listing":"L-1042","output_url":"https://omunafaeewioeslh.public.blob.vercel-storage.com/photos/photo_01kfz3m8q2e4t6v8w0y2a4c6e8/living-room-Xb3kQ9.webp","flagged":false,"confidence":0.86,"report":{"file":"living-room.jpg","input_size":[4032,3024],"output_size":[2048,1536],"distortions_found":{"camera_roll":{"degrees":-1.4,"severity":"moderate"},"vertical_keystone":{"degrees":2.1,"direction":"camera aimed down","severity":"moderate"}},"corrections_applied":{"tilt_correction_deg":2.1,"crop_retained_pct":94.2},"confidence":0.86},"error":null,"created":1794240000}}}}}},"401":{"description":"The API key is missing or invalid. Codes: `missing_api_key`, `invalid_api_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"missing_api_key":{"summary":"No `Authorization: Bearer` header.","value":{"error":{"type":"authentication_error","code":"missing_api_key","message":"Send your secret key as `Authorization: Bearer rth_sk_live_…`.","param":null,"doc_url":null}}},"invalid_api_key":{"summary":"The key is unknown or revoked.","value":{"error":{"type":"authentication_error","code":"invalid_api_key","message":"The API key is invalid or has been revoked.","param":null,"doc_url":null}}}}}}},"404":{"description":"No such photo in your organization. Codes: `resource_missing`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"resource_missing":{"summary":"No photo with that ID in your organization (GET only).","value":{"error":{"type":"invalid_request_error","code":"resource_missing","message":"No photo photo_01kfz3m8q2e4t6v8w0y2a4c6e8.","param":"id","doc_url":null}}}}}}},"429":{"description":"Too many requests for this key. Codes: `rate_limited`.","headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"Over 60 requests a minute per key. Wait `Retry-After` seconds.","value":{"error":{"type":"rate_limit_error","code":"rate_limited","message":"Too many requests for this key. Retry in 12 s.","param":null,"doc_url":null}}}}}}},"500":{"description":"An error on our side. Codes: `internal_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"Something failed on our side. Retry.","value":{"error":{"type":"api_error","code":"internal_error","message":"Something went wrong on our side. Retry the request.","param":null,"doc_url":null}}}}}}}}}}},"components":{"securitySchemes":{"secretKey":{"type":"http","scheme":"bearer","bearerFormat":"rth_sk_live_…","description":"Your organization's secret key, from https://retouch.homes/app/api-keys, sent as `Authorization: Bearer rth_sk_live_…`. Keep it on your server. Rate limit: 60 requests a minute per key."}},"schemas":{"CreatePhotosRequest":{"type":"object","required":["photos"],"properties":{"photos":{"type":"array","minItems":1,"maxItems":40,"description":"Up to 40 http(s) photo URLs, each fetched by our server: JPEG, PNG, WebP and TIFF, at most 25 MB and 25 megapixels. During the beta, the API fetches photos only from hosts on its allowlist; a photo on any other host fails with `photo host … is not allowed`. To add yours, write to hello@retouch.homes.","items":{"type":"string","format":"uri","pattern":"^[Hh][Tt][Tt][Pp][Ss]?://","maxLength":2048}},"listing":{"type":["string","null"],"maxLength":200,"description":"Your own listing ID, stored on each photo and shown in your logs and usage."},"long_edge":{"type":["integer","null"],"minimum":256,"maximum":4096,"description":"The output's longer side in pixels. Left out, the output keeps the edited photo's size."},"format":{"type":["string","null"],"enum":["jpeg","webp",null],"default":"jpeg","description":"The output's file format. The same URL with another `long_edge` or `format` is another photo."},"market":{"type":["string","null"],"description":"The listing's market, as a slug from https://retouch.homes/rules, such as `malaysia`. Window recovery runs only when it's sent and its rules don't ask for a label on AI edits (Singapore, Bay East and Canopy do), since a model finds the windows. Left out, window recovery is off. The same URL with window recovery on is another photo."},"retouch_session":{"type":["string","null"],"pattern":"^embed_[0-9a-z]{26}$","description":"Portals with the Retouch Homes embed only: the embed's `retouch_session` form field. Leave it out otherwise."}}},"Photo":{"type":"object","required":["id","object","status","source_url","listing","output_url","flagged","confidence","report","error","created"],"properties":{"id":{"type":"string","description":"The photo's ID, a TypeID starting `photo_`."},"object":{"type":"string","const":"photo"},"status":{"type":"string","enum":["processing","succeeded","failed"]},"source_url":{"type":"string","description":"The URL you sent, with its query string removed (a signed URL's query string is a credential)."},"listing":{"type":["string","null"],"description":"The `listing` sent with the photo."},"output_url":{"type":["string","null"],"format":"uri","description":"The edited photo, public, once `status` is `succeeded`."},"flagged":{"type":["boolean","null"],"description":"True when `confidence` is below 0.4: look at the photo before publishing it."},"confidence":{"type":["number","null"],"minimum":0,"maximum":1,"description":"How sure the engine is of the result, measured on the edited photo."},"report":{"type":["object","null"],"additionalProperties":true,"description":"What the engine found and changed: sizes, distortions found, corrections applied, residual error and confidence. Fields may be added."},"error":{"oneOf":[{"type":"null"},{"type":"object","required":["type","code","message"],"properties":{"type":{"type":"string","const":"photo_error"},"code":{"type":"string","const":"photo_failed"},"message":{"type":"string","description":"Why this photo failed, e.g. `photo host … is not allowed` or `photo is larger than 25 MB`."}}}],"description":"Set when `status` is `failed`."},"created":{"type":"integer","description":"When the photo was first sent, in Unix seconds."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message","param","doc_url"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","rate_limit_error","api_error"]},"code":{"type":"string","description":"A stable, machine-readable code."},"message":{"type":"string","description":"What went wrong, for a human."},"param":{"type":["string","null"],"description":"The request field at fault, if any."},"doc_url":{"type":["string","null"],"description":"Reserved for a link to the error's docs; null today."}}}}}}}}