Images generated by YouCam AI
The interesting engineering in a face reshape app is not the reshaping. That is one API call. It is the layer above it, where a sentence like "soften my jaw a little and open up the eyes" has to become a set of validated numeric parameters, and where an unconstrained language model will happily invent a field name that does not exist and an intensity nobody asked for.
This is the walkthrough for building that layer properly: what the AI Face Reshape API can actually change, how to constrain a model's output to a schema the API will accept, why there is a detection step before the task, how to keep the key off the client, and the responsible use questions that this particular API deserves and rarely gets.
What this guide covers
What Are You Building?
A web app where a user uploads a photo, describes the change they want in plain language, picks an intensity, and compares the original against the result. Underneath that are five pieces: an upload, a language to parameter translation, an optional face detection step, the reshape task, and a polling loop.
Four of those five are routine. The translation step is the one worth designing deliberately, and it is also the one a coding agent will implement most loosely if you let it.
What Can the API Actually Change?
Twenty adjustable attributes across four groups. Knowing the exact list matters, because it is also the vocabulary your language layer is allowed to produce.
| Group | Attributes | Count |
|---|---|---|
| Eyes | Eye width, eye height, eye distance, eyelid angle | 4 |
| Face | Face size, chin shape, chin length, width, cheekbones, jawline | 6 |
| Nose | Nose size, nose width, nose bridge, nose tip position, nose wing width, nose height | 6 |
| Lips | Lip size, lip width, lip height, lip peak refinement | 4 |
The sibling AI Face Lift API in the same family uses a 0 to 100 scale per adjustable area, with each step increasing the effect gradually, and its documentation states the system is built to preserve the person's identity rather than produce exaggerated results. That framing is worth carrying into your own product: the useful range for most users sits well below the maximum.
Why Is Natural Language the Interesting Part?
Because the mapping is genuinely ambiguous and the failure modes are quiet.
"Make my jaw softer" has to become a specific attribute and a specific number. Is softer a decrease in jawline definition, a change in chin shape, or both? How much is "a little"? And what should happen when the user asks for something the API cannot do at all, such as changing their skin tone or removing a scar?
An unconstrained prompt that asks a model to output API parameters will, on a long enough timeline, produce a field that does not exist, a value outside the valid range, an attribute from a different API in the family, or a confident attempt at a request that should have been refused. None of those throw an error in your language layer. They throw an error at the API, or worse, they silently apply something the user did not ask for.
How Do You Constrain the Model's Output?
Three mechanisms, and you want all three rather than whichever one your agent reaches for first.
| Mechanism | What it prevents | Where it lives |
|---|---|---|
| A closed enum of attribute names | Invented fields and attributes borrowed from other APIs | Schema, enforced on the server |
| Clamped numeric ranges | Out of range values and silently extreme results | Validation after the model, before the API |
| An explicit refusal path | Requests outside the API's capability being approximated | The schema itself, as a first class outcome |
The refusal path is the one people leave out. If the only shape your language layer can return is a set of adjustments, then every request becomes a set of adjustments, including the ones that should have been answered with "this tool cannot do that". Make refusal a valid return value and the problem disappears.
A workable schema looks roughly like this:
{
"type": "object",
"properties": {
"outcome": { "enum": ["adjust", "out_of_scope", "needs_clarification"] },
"message": { "type": "string" },
"adjustments": {
"type": "array",
"maxItems": 4,
"items": {
"type": "object",
"properties": {
"attribute": {
"enum": ["eye_width", "eye_height", "eye_distance", "eyelid_angle",
"face_size", "chin_shape", "chin_length", "face_width",
"cheekbones", "jawline", "nose_size", "nose_width",
"nose_bridge", "nose_tip_position", "nose_wing_width",
"nose_height", "lip_size", "lip_width", "lip_height",
"lip_peak"]
},
"intensity": { "type": "integer", "minimum": 0, "maximum": 100 }
},
"required": ["attribute", "intensity"]
}
}
},
"required": ["outcome"]
}Three design decisions in there are deliberate. The attribute list is a closed enum rather than a free string, so a hallucinated field fails schema validation rather than reaching the API. The maxItems cap stops a single vague sentence from turning into fourteen simultaneous adjustments. And outcome is required, which forces the model to commit to whether this is even an in scope request before it starts producing numbers.
Then clamp again on the server after validation. Schema validation and range clamping are cheap, and the alternative is discovering the gap through a support ticket.
How Does the Call Flow Work?
| Step | What happens | Notes |
|---|---|---|
| 1. Provide the image | Register the file to get an id and an upload URL, then upload the bytes, or pass a public URL | Registering without uploading is a common silent failure |
| 2. Detect faces | Call the pre-process endpoint when the image may contain more than one face | Returns bounding boxes you choose from |
| 3. Create the task | POST the image reference, the chosen face, and the adjustment configuration | Returns a task id, not an image |
| 4. Poll | GET the task by id until the status resolves to success or error | Processing is asynchronous throughout |
| 5. Present the comparison | Show original and result together | A before and after view is the whole product |
The sibling Face Lift API in this family runs against the yce-api-01.makeupar.com base, with the file registration at the shared file endpoint, a dedicated pre-process endpoint for detection, and a task endpoint that returns an id you then poll. Treat that as the shape to expect, and read the OpenAPI bundle linked from the reference for the exact field names rather than guessing them, since the human readable page does not spell out every one.
Why Is There a Detection Step?
Because the API cannot know which face you mean.
When an image contains more than one face, the pre-process call detects them and returns a bounding box for each, expressed in pixel coordinates with a left, top, width and height. You pick one, and the index of your choice is what the task call carries.
Two practical consequences. First, build the detection step into your flow from the beginning rather than adding it when a user uploads a group photo, because retrofitting a face picker into a UI that assumed one face is more work than including it. Second, use the bounding boxes in your interface, not just internally. Drawing them and letting the user tap the right face is both better UX and a free confirmation that you are about to edit the person they intended.
What Are the Image and Pose Requirements?
Based on the documented requirements for the sibling Face Lift API in the same family:
| Requirement | Value |
|---|---|
| Formats | JPG, JPEG or PNG |
| File size | Under 10 MB |
| Dimensions | Long side 1920 px or less |
| Face size | More than 32 by 32 px when the long edge is 640 px |
| Head roll | Within plus or minus 75 degrees |
| Head yaw | Within plus or minus 90 degrees |
| Content | A clearly visible face |
The pose tolerances are notably generous compared with other APIs in this family, which is useful to know because it means you can accept photos a makeup try on would reject. Validate format, size and dimensions client side anyway, since those three are free to check and save a round trip.
What Do the Error Codes Mean?
| Code | What happened | What to tell the user |
|---|---|---|
| PHOTO_CHECK_INVALID | The pose or size of the photo is outside the accepted range | Name the issue. Face too small, or head turned too far |
| PHOTO_DETECTION_FAIL | The photo could not be processed correctly | Suggest a clearer, better lit photo of one face |
| INPUT_MAIN_IMAGE_EMPTY | No user image was supplied | Your bug. Log it and show a generic retry |
| INPUT_ERROR | The input file format is incorrect | Catch this client side. The user should never see it |
| OBJECT_DETECTION_FAIL | A secondary image could not be processed | Relevant only in flows that supply one |
| RUNTIME_ERROR | An unexpected failure in the runtime | The only one that deserves a plain retry |
How Do You Keep the API Key Off the Client?
By never letting it reach the browser. The key authenticates as a bearer token, which means anyone who can read it can spend your units, so it belongs in a server environment variable and the API call belongs behind your own route.
// app/api/reshape/route.ts
export async function POST(req: Request) {
const { fileUrl, adjustments } = await req.json();
const safe = validateAdjustments(adjustments); // enum + clamp, server side
if (!safe.ok) {
return Response.json({ error: safe.reason }, { status: 400 });
}
const res = await fetch(`${process.env.YOUCAM_API_BASE}/s2s/v2.0/task/face-reshape`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.YOUCAM_API_KEY}`
},
body: JSON.stringify({ src_file_url: fileUrl, effects: safe.value })
});
return Response.json(await res.json(), { status: res.status });
}Three things that route should do beyond proxying. Re-validate the adjustments server side even though you validated them when the model produced them, because the client is not a trustworthy source. Rate limit per session, since an unprotected proxy is an unprotected wallet. And never forward the raw upstream error body to the client, because errors sometimes carry more detail about your account than you want public.
What About Live Capture?
There is a JavaScript Camera Kit for in browser capture, and its facereshape detection mode covers this API and Face Lift. It runs quality checks for distance, angle and lighting before an image is captured, and returns the result through an event as base64 or a Blob.
It needs getUserMedia support, HTTPS except on localhost, and a mount element in the page. The reason to use it rather than a plain file input is that it prevents most of the pose and size errors at capture time, which is considerably cheaper than explaining them afterwards.
What Does the Coding Agent Usually Get Wrong?
| Mistake | What you see | The fix |
|---|---|---|
| Free text attribute names from the model | Rejected requests with field names that look plausible | A closed enum, validated before the call |
| No refusal path | Out of scope requests become arbitrary adjustments | Make refusal a valid outcome in the schema |
| Skipping the detection step | Works until someone uploads a photo with two people in it | Build the face picker in from the start |
| Calling the API from the browser | The key is visible in network inspection | Proxy through your own server route |
| Treating the task as synchronous | A task id rendered where an image should be | Poll until the status resolves |
| Registering a file without uploading the bytes | Errors that point at the endpoint rather than the cause | Upload before creating the task |
| Defaulting intensity high | Results that look obviously edited and lose the user | Default low. The useful range is well below maximum |
That last row matters more than it looks. The difference between a tool people enjoy and a tool people distrust is frequently just the default intensity, and agents tend to pick a round number in the middle because nothing told them otherwise.
How Should You Handle This Responsibly?
This API edits what someone's face looks like. That is a different category of product from a makeup filter, and a few decisions made early will save you a difficult conversation later.
- Edit only photos the user has the right to edit. If your product accepts uploads of other people, say plainly in the interface that consent is the uploader's responsibility, and do not build features that encourage editing images of people who are not present.
- Label simulated results as simulations. Especially in aesthetic consultation contexts, a rendered image is an illustration of a possible direction, not a forecast of a clinical outcome. Say so in the interface, not in a terms page nobody reads.
- Never present a simulation as a real before and after. Using generated results in marketing as though they were achieved outcomes is misleading, and in a medical or aesthetic context it is the kind of misleading that attracts regulators.
- Keep the defaults modest and the original always visible. A comparison view that keeps the real photo on screen is both better product design and a steady reminder of what is actually being changed.
- Be deliberate about minors. Face editing aimed at under eighteens deserves a considered position rather than an accidental one.
- Handle the images like the sensitive data they are. Define retention, delete when you say you will, and do not quietly keep a copy for a future model.
None of this slows a build down meaningfully. All of it is substantially harder to add after launch than before.
Face Reshape API FAQ
What is the AI Face Reshape API?
An image API that adjusts facial features in a photo and returns a rendered result. It covers 20 adjustable attributes across the eyes, face, nose and lips, and returns a result image URL and an id.
How do I turn a user's sentence into API parameters?
Have the model emit structured output against a schema with a closed enum of valid attribute names, integer ranges, a cap on how many adjustments one request can contain, and an explicit outcome field that allows refusal. Then validate and clamp again on your server.
Why does my language layer sometimes produce invalid fields?
Because an unconstrained model will approximate rather than refuse. The fix is structural rather than better prompting: a closed enum makes an invented field fail validation locally instead of reaching the API.
Do I need the detection step?
You need it whenever an image might contain more than one face. It returns a bounding box per face and you pass the index of the one you want. Building it in from the start is far easier than retrofitting a face picker later.
How do I keep my API key secure?
Keep it in a server environment variable and call the API from your own backend route rather than the browser. The key is a bearer token, so anyone who can read it can spend your units. Rate limit the route and do not forward raw upstream errors to the client.
Is the reshape call synchronous?
No. Creating a task returns an id and you poll until it resolves. The same pattern applies across this family of APIs.
What photo requirements are there?
For the closely related Face Lift API: JPG, JPEG or PNG under 10 MB, long side 1920 px or less, a clearly visible face above the minimum size, with head roll within 75 degrees and yaw within 90. The pose tolerances are more generous than makeup try on requires.
What intensity should I default to?
Low. The scale runs 0 to 100 and the system is designed to preserve identity rather than exaggerate, so the range most users find useful sits well below the middle. A high default is the fastest way to make the tool feel untrustworthy.
Can I use the results in marketing?
Not as real before and after images. Simulated results should be labelled as simulations, particularly in aesthetic or medical contexts, where presenting a render as an achieved outcome is misleading and carries regulatory risk.
Can I build this with a coding agent?
Yes, and the build is short. Write the parameter schema yourself rather than letting the agent infer it, make refusal a first class outcome, and review the key handling and the default intensity, because those are the three things an agent will get loosely right and you will care about.
The Schema Is the Product
A face reshape app is one API call wrapped in a translation layer, and the quality of the translation layer is the quality of the app. Give the model a closed enum instead of a free string, cap how many changes one sentence can produce, make refusal a valid answer, and clamp everything again on the server where the client cannot reach it. Build the face picker before you need it, keep the key behind your own route, and default the intensity low.
Then spend a little time on the part that is not engineering: say what the simulation is, keep the original on screen, and be clear about whose face you are willing to edit. That is the difference between a demo and something you are comfortable shipping.
Get a free API key and test face reshape in the YouCam AI Playground, read the MCP documentation if you want your coding agent to discover the tools directly, or contact Perfect Corp to talk through a production integration.
Author: 





