> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.morphiaone.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.morphiaone.ai/_mcp/server.

# Hair color try-on (preview)

> **Info**
>
> **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](/products/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](/concepts/assets). 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](/sdks/overview):

```ts
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](/sdks/overview):

```python
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](/concepts/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](/concepts/assets) and
[Jobs](/concepts/job-outputs) for those steps.

```bash
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.