Skip to navigation

Makeup try-on

Render foundation, lipstick and lip liner in the layer order you choose.

Release status. API 1.0.0-rc.1 is a release candidate: request and response shapes may still change before the stable release. SDK 0.2.2 is published as a Developer Preview (@morphiaone/sdk on npm and morphiaone on PyPI).

Calls need a key with the makeup.apply scope, in an environment your account is provisioned for; see product availability.

POST /v1/makeup/try-ons renders products onto an uploaded face image. Upload the image as an Asset first. Product requests run as asynchronous Jobs.

TypeScript SDK

Assume client is configured as shown in the SDK guide:

import { readFile, writeFile } from "node:fs/promises";
const source = await client.assets.upload(await readFile("face.jpg"));
const job = await client.makeup.tryOn({
assetId: source.id,
products: [
{ category: "lip_liner", color: "#7B2E42", opacity: 0.8 },
{ category: "lipstick", color: "#C2185B" },
],
idempotencyKey: "session-91c4-makeup",
});
const done = await client.jobs.wait(job.id);
if (done.output?.known && done.output.type === "makeup.try_on") {
const image = await client.assets.getContent(done.output.image);
await writeFile("makeup.png", image.content);
}

Python SDK

Assume client is configured as shown in the SDK guide:

from morphiaone import MakeupTryOnOutput
source = client.assets.upload("face.jpg")
job = client.makeup.try_on(
asset_id=source.id,
products=[
{"category": "lip_liner", "color": "#7B2E42", "opacity": 0.8},
{"category": "lipstick", "color": "#C2185B"},
],
idempotency_key="session-91c4-makeup",
)
done = client.jobs.wait(job.id)
if isinstance(done.output, MakeupTryOnOutput):
image = client.assets.get_content(done.output.image)
with open("makeup.png", "wb") as out:
out.write(image.content)

jobs.wait returns only after success and raises ProcessingError for a failed job. Pass a stable idempotency key for retries across processes; both SDKs generate a key when omitted, but that protects only retries inside one call. See Idempotency.

REST

Upload the image as raw bytes with POST /v1/assets, then submit its asset_id. The request below returns a Job; persist its id, poll GET /v1/jobs/{job_id} until succeeded or failed, and download the result Asset before it expires. See Assets and Jobs for those steps.

curl --fail-with-body --silent --show-error \
-H "X-API-Key: ${API_KEY}" \
-H "Idempotency-Key: session-91c4-makeup" \
-H "Content-Type: application/json" \
-d '{
"inputs": [{"role": "source", "asset_id": "asset_6pa8ad0ds2t7g1z6pg4tcr4zqp"}],
"products": [
{"category": "lip_liner", "color": "#7B2E42", "opacity": 0.8},
{"category": "lipstick", "color": "#C2185B"}
]
}' \
"${BASE_URL}/v1/makeup/try-ons"

Products in the V1 draft

products is required and ordered: its order is the layer order. Reordering it changes the request.

categoryProduct
foundationFoundation
lipstickLipstick
lip_linerLip liner

These are the three categories in the current draft. Each product has a category, a color as #RRGGBB, and optional opacity from 0 to 1 (default 1, quantized to 0.01). Each category appears at most once per request, so a request holds at most three products. A repeated category is a 400 INVALID_REQUEST whose details[].code is duplicate_product_category; an unknown category or malformed colour is a 400 INVALID_REQUEST naming the field. The draft launch set remains an open decision.

Reading the output

A succeeded job’s output.type is makeup.try_on, and it carries one result image:

{
"type": "makeup.try_on",
"revision": 1,
"image": {
"id": "asset_eyh67f8tc55kdrayf5zka7d7e7",
"role": "result_image",
"media_type": "image/png",
"width": 1200,
"height": 1600,
"size_bytes": 932182,
"expires_at": "2026-10-01T10:00:05Z",
"href": "/v1/assets/asset_eyh67f8tc55kdrayf5zka7d7e7/content"
}
}

Fetch href with your API key before expires_at. It is an authenticated download from the API itself, not a CDN URL—see Privacy and retention.

Visual parity status

Pixel parity against the production browser renderer has not been verified for any category. Current reference authority is blocked by a confirmed output-capture defect; existing pixels are historical evidence only. A successful job is not evidence that its output matches an existing browser try-on experience; do not make that claim from this draft API.

Interactive sessions

Each call is an independent submission with its own Job; there is no server-side session that accumulates products. Debounce interactive controls before submitting, and use a stable idempotency key when retrying the same logical try-on. Upload the face once and reuse its asset_id while it remains unexpired.