Make your first call
Send a photo, get three PNGs back. Everything is a normal HTTPS request; the key goes in the X-API-Key header.
# photo in → style portrait + 2 die-cut templates out
curl -X POST https://figuroos-api.appypie.workers.dev/v1/portrait \
-H "X-API-Key: figuroos_live_dd8992f2d2c11cf854d7c01a" \
-F image=@customer.jpg \
-o result.json
/v1/portrait call takes about 45 s – 3 min. Set your client timeout to at least 240 s. Images come back as short https://…/img/… URLs on our domain — open or download them directly (they auto-expire after 48 h).API key
Send your key on every request in the X-API-Key header. Authorization: Bearer <key> is also accepted. /health is the only route that needs no key.
| Header | Value |
|---|---|
X-API-Key | figuroos_live_dd8992f2d2c11cf854d7c01a ← QA test key |
401.Three routes
Liveness probe. Returns instantly.
{ "status": "ok", "service": "Figuroos Portrait API" }The full pipeline. Send a customer photo; get back the Figuroos-style portrait plus both die-cut templates. The service reads the photo (gender, age, facial hair, headwear) so the output is faithful to the actual person.
Request — application/json or multipart/form-data
Send the photo either as a JSON body (base64) or as a file upload — both work.
{ "image_base64": "<base64 of the photo — raw or a data: URI>",
"strength": 0.85, "scale": 2.4 }| Field | Type | Default | Notes |
|---|---|---|---|
image | file | — | the customer photo (JPG / PNG / WebP). Either this or image_base64. |
image_base64 | string | — | raw base64 or a data: URI, as an alternative to image |
strength | float | 0.85 | how far to push toward the illustration (0.7 truer to photo ↔ 0.95 flatter) |
scale | float | 2.4 | style strength (≈2.0–2.6 clean flat-vector; >3 distorts) |
guidance | float | 4.0 | prompt adherence |
steps | int | 30 | refinement steps |
Response — 200
{
"style_face": "https://figuroos-api.appypie.workers.dev/img/<id>/style_face.png",
"template_die_cut": "https://figuroos-api.appypie.workers.dev/img/<id>/template_die_cut.png",
"template_net": "https://figuroos-api.appypie.workers.dev/img/<id>/template_net.png",
"meta": {
"subject": "an elderly man with a full white beard, a black beanie, fair skin",
"hair_rgb": [38, 40, 47], "aniso": 1.12, "bg_fill_pct": 18.9
}
}meta.subject is how the service read the photo — a fast way for QA to check the input was understood correctly (right gender / age / beard).
Skip generation. Send an already Figuroos-styled face and get just the two die-cut templates back. Fast (~1–3 s).
| Field | Type | Notes |
|---|---|---|
image / image_base64 | file / string | a styled portrait on a plain background |
Returns template_die_cut, template_net, and meta — same shape as above, minus style_face.
The outputs
| Field | What it is |
|---|---|
| style_face | Figuroos-style portrait, flat vector on white |
| template_die_cut | face panel cut to the oval — transparent outside the cut line, keyline included |
| template_net | head net; crown/side panels filled in the sitter’s hair colour, ear-holes & glue tabs preserved |
All three are PNG, print-resolution (~1600 px on the long edge). Each field is a URL on our domain; the links auto-expire after 48 h.
// each field is a URL — open it, drop it in an <img>, or download it
const data = await res.json();
img.src = data.template_die_cut; // <img src> works as-is
# or save to a file (curl + jq)
curl -o face.png "$(jq -r .style_face result.json)"Status codes
X-API-Key. Fix the key./health, /v1/portrait, /v1/diecut exist).Every response is JSON — success and error alike. Error bodies are always { "error": "…" }, deliberately generic so they never expose anything about the backend.
Good to know
| Item | Value |
|---|---|
| Input photo | front-facing, face clearly visible, min ~512 px; JPG / PNG / WebP; ≤ 20 MB |
/v1/portrait latency | ~45 s – 3 min (first call after idle is slower — the service wakes on demand) |
/v1/diecut latency | ~1–3 s |
| Client timeout | set ≥ 240 s for /v1/portrait |
| Output | PNG, ~1600 px long edge, print-ready — returned as /img/… URLs that auto-expire after 48 h |
| Determinism | generative — the same photo twice gives two valid but different results |
What to verify
Grouped by area. The right-hand note is the expected result. Faithfulness (§Generation) is the one that matters most — the output must be the same person.
Auth & health
- GET
/healthwith no key200 · ok - POST
/v1/portraitwith no key401 - POST with a wrong key401
- POST with the valid key200
Happy path
- Clean front-facing selfieopen all three returned URLs → valid, non-empty PNGs200 · 3 URLs
image_base64instead of a file uploadsame photo, same result shape200/v1/diecutwith an already-styled face200 · 2 PNGs
Generation faithfulness — the key one
- Elderly man with a beardoutput is an old bearded man — not a young/clean-shaven facematches
- Woman, long hairgender, hair length & colour preservedmatches
- Range: young/old, fair/dark skin, with & without glasses/hatmatches
- Check
meta.subjectreflects the actual photoquick signal the input was read correctlyaccurate - No invented accessories (sunglasses, hats not in the photo)none
Output validity
style_face— recognisable, flat-vector, white background✓template_die_cut— face-only oval, keyline, transparent outside the cut✓template_net— panels in hair colour, ear-holes & tabs intact, face seated✓- All outputs ~1600 px long edgeprint-res
Edge cases (should fail gracefully, never crash)
- Image with no face (landscape, object)422 / 502
- Non-image file (e.g. a .txt renamed .jpg)422
- Very small (<512 px) and very large (~20 MB) photoshandled
- Busy / cluttered backgroundoutput is still a single clean subjectsingle
- Group photo (multiple faces)picks the main subjectone face
Timing & leakage
- Portrait completes within ~3 min; no premature client timeout≤ 240 s
- Second call after idle (cold start) still succeeds200
- Responses & error bodies expose no internal/provider names or URLsclean