Every operation is documented pixel math. The same call always returns the same image - byte for byte, and no AI ever invents content. This page is rendered from the engine's own catalog, so it can never drift from the code.
# One call: upload, adjust, download. Output is JPEG, EXIF stripped. curl -X POST https://retouchengine.com/v1/edit \ -H "X-API-Key: rte_..." \ -F "image=@photo.jpg" \ -F 'ops=[{"op":"autotone"},{"op":"clarity","amount":0.5}]' \ -o edited.jpg
Response headers tell you exactly what happened: X-Ops-Applied (the applied chain),
X-Elapsed-Ms, X-Output-Size, X-Key-Usage (today's usage / your daily limit).
Send your key in the X-API-Key header. Create a profile to get a
trial key instantly (25 requests/day): upgrade any time on Pricing.
Missing/invalid key → 401 · over daily limit → 429 (resets midnight UTC).
POST /v1/edit, multipart form: image (JPEG/PNG/WebP, ≤ 40 MB, ≤ 40 MP),
ops (JSON list, up to 20 operations, applied in order), optional preset
(runs before ops), quality (50–100, default 92) and max_edge
(downscale the long edge, e.g. 2000 for web delivery). Nothing is ever stored on our servers.
exposureExposure in EV stops (-2..2) with a soft highlight roll-off, never blows out.
| param | range | default | |
| ev | -2.0 … 2.0 | 0.0 | Exposure in EV stops; + never blows out |
contrastContrast -1..1 as an S-curve around the midpoint (0.5).
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | S-curve around the midpoint |
highlightsHighlights -1..1: negative recovers blown areas, positive lifts them.
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | − recovers blown areas, + lifts them |
shadowsShadows -1..1: positive opens dark areas without greying true black.
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | + opens dark areas without greying black |
blackpointBlack point 0..0.25: adds depth to flat images (0.06 is the classic).
| param | range | default | |
| amount | 0.0 … 0.25 | 0.0 | Depth for flat images |
autotoneDarkroom baseline: careful levels (1.6x cap, flat scenes skipped),
| param | range | default | |
| strength | 0.0 … 1.0 | 1.0 | Darkroom baseline: levels+gamma+S-curve+vibrance |
gammaRaw gamma 0.5..2.0 (1 = unchanged).
| param | range | default | |
| value | 0.5 … 2.0 | 1.0 | Raw gamma |
saturationSaturation -1..1 (-1 = black & white).
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | -1 = black & white |
vibranceVibrance -1..1: boosts ONLY muted colors, skin and saturated areas are barely touched.
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | Boosts only muted colors |
warmthWarmth -1..1: + toward golden, − toward cool. Channel gains, luma-preserving.
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | + golden, − cool (luma-preserving) |
tintTint -1..1: + toward magenta, − toward green (luma-preserving).
| param | range | default | |
| amount | -1.0 … 1.0 | 0.0 | + magenta, − green |
monoClassic black & white with a gentle tone curve.
| param | range | default | |
| enabled | bool | True | Classic black & white |
clarityDarkroom clarity v2: luminance-only, midtone-weighted, delta cap
| param | range | default | |
| amount | 0.0 … 1.0 | 0.5 | Luminance clarity with shadow protection and halo cap |
sharpenSharpen 0..1 (unsharp mask on luminance, fine radius).
| param | range | default | |
| amount | 0.0 … 1.0 | 0.3 | Unsharp mask on luminance |
denoiseDenoise 0..1 (edge-preserving bilateral, mild).
| param | range | default | |
| amount | 0.0 … 1.0 | 0.3 | Edge-preserving denoise |
cropCrop [x0,y0,x1,y1] in pixels (clamped; min 100×100).
| param | range | default | |
| rect | rect | None | [x0,y0,x1,y1] in pixels |
aspectCrop to aspect ('3:2','2:3','1:1','4:5','16:9'), anchor center/top.
| param | range | default | |
| ratio | 3:2,2:3,1:1,4:5,16:9 | 3:2 | Crop to aspect ratio |
| anchor | center,top | center | Anchor |
rotateFree rotation -15..15°: auto-cropped to content (no black wedges).
| param | range | default | |
| degrees | -15.0 … 15.0 | 0.0 | Free rotation, auto-cropped |
flipMirror: horizontal | vertical.
| param | range | default | |
| direction | horizontal,vertical | horizontal | Mirror |
vignetteVignette 0..1: softly darkened corners, the subject untouched.
| param | range | default | |
| amount | 0.0 … 1.0 | 0.3 | Darkened corners |
grainFilm grain 0..1: deterministic (fixed seed), luminance grain.
| param | range | default | |
| amount | 0.0 … 1.0 | 0.2 | Deterministic film grain |
| seed | 0 … 9999 | 7 | Fixed seed = identical grain |
textPoster-style caps overlay in white with a soft dark fade, reads on any background.
| param | range | default | |
| text | str | The text (caps, ≤80 chars) | |
| position | bottom,top,top-left,top-right,bottom-left,bottom-right | bottom | Placement |
| size | 0.0 … 0.15 | 0.0 | Text height vs. width (0 = auto) |
watermarkTiled diagonal watermark, visible enough to protect, light enough to ignore.
| param | range | default | |
| text | str | Watermark text (≤60 chars) | |
| opacity | 0.03 … 0.5 | 0.16 | Strength |
| size | 0.012 … 0.08 | 0.028 | Text height vs. width |
| angle | -45.0 … 45.0 | -22.0 | Tile angle |
logoOverlay an uploaded PNG logo (its alpha is the artwork) in a corner or centered.
| param | range | default | |
| position | bottom,top,top-left,top-right,bottom-left,bottom-right | bottom-right | Placement |
| size | 0.0 … 0.3 | 0.0 | Logo height vs. width (0 = auto) |
| opacity | 0.1 … 1.0 | 0.95 | Opacity |
| mode | white,original | white | white = monochrome w. shadow · original = the PNG's own colors |
person_lightBrighten only the person: alpha-bounded screen curve, by construction
| param | range | default | |
| ev | 0.0 … 1.0 | 0.35 | Light ONLY on the person (model mask) |
cutoutCut the subject out and place it on a clean background. Removes only, never invents.
| param | range | default | |
| model | u2net,isnet-general-use,silueta,u2net_human_seg | u2net | Which mask model finds the subject |
| background | str | white | Colour name or hex, or 'transparent' with format=png |
| edge | 0.0 … 1.0 | 0.5 | Pushes the half-transparent grey haze to zero; keeps real soft edges |
| shrink | 0.0 … 6.0 | 1.0 | Pulls the edge in by N pixels so no old background is left in the rim |
| feather | 0.0 … 8.0 | 1.0 | Softens the cut so it does not look stamped out |
| despill | 0.0 … 1.0 | 0.8 | Removes the old background colour from edge pixels (no grey halo) |
| keep_parts | 0.0 … 1.0 | 0.15 | Drops loose blobs smaller than this share of the main subject; 0 keeps everything |
fitPad to a marketplace format so the subject fills a set share of the frame.
| param | range | default | |
| ratio | str | 1:1 | Target frame, e.g. 1:1, 4:5, 3:2, 16:9 |
| fill | 0.3 … 1.0 | 0.85 | How much of the frame the subject fills |
| background | str | white | Padding colour, name or hex |
| anchor | center,top,bottom | center | Where the subject sits in the frame |
Named recipes, run alone or as a starting point before your own ops.
baselineautotone
punchyautotone → clarity amount=0.5 → vibrance amount=0.2
soft_portraitautotone strength=0.6 → warmth amount=0.15 → shadows amount=0.15 → denoise amount=0.2
classic_bwautotone → mono → clarity amount=0.35 → grain amount=0.25 → vignette amount=0.25
golden_hourautotone → warmth amount=0.3 → vibrance amount=0.15 → vignette amount=0.2
subject_popautotone → person_light ev=0.35 → clarity amount=0.4
AI for inspection, old school code for retouch. The model looks at your photo and a style brief, then recommends concrete catalog settings. It never touches a pixel: the recommendation is a plain ops chain, and the edit that runs it is fully deterministic. Note the honest split, the recommendation may vary between calls (it is advice); the edit of a given chain never does.
# Get a recommendation (JSON chain you can inspect, store, replay) curl -X POST https://retouchengine.com/v1/analyze -H "X-API-Key: rte_..." -F "image=@photo.jpg" -F "prompt=candid style editing with a bit of glamour" # Or apply it in the same call and get the finished JPEG curl -X POST https://retouchengine.com/v1/analyze -H "X-API-Key: rte_..." -F "image=@photo.jpg" -F "prompt=warm editorial look" -F "apply=true" -o edited.jpg # X-Ops-Applied shows exactly which deterministic chain ran
Save your own named chains and use them exactly like built-in presets.
# Save (or replace) a preset, chains may include your custom ops curl -X POST https://retouchengine.com/v1/presets -H "X-API-Key: rte_..." -H "Content-Type: application/json" -d '{"name":"my_look","chain":[{"op":"autotone"},{"op":"warmth","amount":0.2}]}' # Use it curl -X POST https://retouchengine.com/v1/edit -H "X-API-Key: rte_..." -F "image=@photo.jpg" -F "preset=my_look" -o edited.jpg
GET /v1/presets lists yours · DELETE /v1/presets/<name> removes one.
Your presets shadow built-ins with the same name.
Need something the catalog doesn't have, say, highlight beards? Describe it and the engine's AI writes the operation as code you can read. Every version is stored, inspectable and callable forever, and the code passes three gates before it can ever run: a static allow-list (only numpy/cv2/math, no file or network access, no reflection), a resource-capped sandbox process, and a determinism check, the op must produce byte-identical output on repeated runs.
# 1. Create an operation from natural language curl -X POST https://retouchengine.com/v1/custom-ops -H "X-API-Key: rte_..." -H "Content-Type: application/json" -d '{"name":"beard_glow","request":"gently brighten beard areas on faces"}' # 2. Use it in any chain (v optional, defaults to latest) curl -X POST https://retouchengine.com/v1/edit -H "X-API-Key: rte_..." -F "image=@photo.jpg" -F 'ops=[{"op":"autotone"},{"op":"custom:beard_glow","strength":0.5}]' -o out.jpg # 3. Iterate, every request becomes a NEW version; old ones stay callable curl -X POST https://retouchengine.com/v1/custom-ops/beard_glow/versions -H "X-API-Key: rte_..." -H "Content-Type: application/json" -d '{"request":"softer edges and a touch stronger"}' # pin an old version in a chain: {"op":"custom:beard_glow","v":1}
Custom ops receive ctx["person_alpha"] (a person mask) and
ctx["face_boxes"] (face rectangles) so they can target people and faces -
deterministically, never generatively. GET /v1/custom-ops lists yours with all
versions; GET /v1/custom-ops/<name>/code returns the code itself.
Codegen has its own daily allowance (trial 5 · starter 25 · pro 100).
GET /v1/ops returns this whole catalog as JSON (params, ranges, defaults, presets, limits) -
build UIs against it. OpenAPI schema at /openapi.json, interactive explorer at
/api-explorer.
| 400 | Bad ops JSON, unknown op, or empty chain, message says exactly what | ||
| 401 | Missing/invalid API key | ||
| 413 | File over 40 MB or image over 40 MP | ||
| 415 | Not a decodable image | ||
| 429 | Daily limit reached (see X-Key-Usage) | ||
| 503 | A model-backed op (person_light) is temporarily unavailable | ||