Skip to navigation

Hair color try-on (preview)

Recolor the hair in a photo with a two-stop color gradient.

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 hair.apply scope, in an environment your account is provisioned for; see product availability. Hair finds the hair in the photo and recolors only those pixels; it does not change haircut or length, apply a reference hairstyle, or generate candidates.

Upload one source image as an Asset. That is all the endpoint needs.

If you already segment hair yourself, you may add one grayscale hair-mask Asset in the hair_mask role; the service then uses your mask instead of its own. The mask must have the same dimensions as the source. Zero pixels mean background; nonzero pixels mean hair.

The only rendering option is a closed color_gradient with dark and light stops. A stop has an #RRGGBB color and optional opacity from 0 to 1 (default 1, rounded to two decimal places). The stops are applied against source hair luminance. Raw engine indices, textures, arbitrary configuration, and hairstyle references are not accepted.

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("portrait.png"));
const job = await client.hair.tryOn({
sourceAssetId: source.id,
colorGradient: {
dark: { color: "#3B1F16", opacity: 0.9 },
light: { color: "#C87943", opacity: 0.8 },
},
idempotencyKey: "order-42-hair",
});
const done = await client.jobs.wait(job.id);
if (done.output?.known && done.output.type === "hair.try_on") {
const image = await client.assets.getContent(done.output.image);
await writeFile("hair-recolored.png", image.content);
}

Python SDK

Assume client is configured as shown in the SDK guide:

from pathlib import Path
from morphiaone import HairTryOnOutput
source = client.assets.upload("portrait.png")
job = client.hair.try_on(
source_asset_id=source.id,
color_gradient={
"dark": {"color": "#3B1F16", "opacity": 0.9},
"light": {"color": "#C87943", "opacity": 0.8},
},
idempotency_key="order-42-hair",
)
done = client.jobs.wait(job.id)
if isinstance(done.output, HairTryOnOutput):
image = client.assets.get_content(done.output.image)
Path("hair-recolored.png").write_bytes(image.content)

Both SDKs return a typed result for output.type: "hair.try_on"; jobs.wait raises ProcessingError when the job fails. Pass a stable idempotency key for retries across processes. See Idempotency.

REST

Use an API key with hair.apply scope. Upload the source with POST /v1/assets, then submit its Asset id (add a hair_mask input only to send your own mask). The request returns a Job; persist its id, poll GET /v1/jobs/{job_id} until succeeded or failed, then download the result 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: order-42-hair" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{"role": "source", "asset_id": "asset_6pa8ad0ds2t7g1z6pg4tcr4zqp"}
],
"color_gradient": {
"dark": {"color": "#3B1F16", "opacity": 0.9},
"light": {"color": "#C87943", "opacity": 0.8}
}
}' \
"${BASE_URL}/v1/hair/try-ons"

Reusing an idempotency key with a changed source, mask or gradient returns 409 IDEMPOTENCY_KEY_REUSED.

Result

The output carries one result image Asset and the normalized applied.color_gradient. Download the result through the SDK or GET /v1/assets/{result_asset_id}/content with the same API key. Result Asset and job share expiry; content requires authentication and does not expose a storage URL.