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

# Test mode

A test key starts with `pk_test_`. A live key starts with `pk_live_`. Both use the same
header, the same base URL and the same operations, so your integration code does not
change between them. Only the key does.

Test mode is available only where it is turned on for your account. When it is, the
**API keys** page of the [Platform](https://platform.morphiaone.ai/login) shows a
**Live / Test** switch, and the create form lets you choose the key's environment. Keys created under **Test** have a `TEST` badge.

## Same lifecycle, fixture results

A test job goes through the same steps as a live job. It is queued, picked up, moves to
`running`, settles, writes its result assets and expires on the same schedule. Polling,
`Retry-After`, idempotency and result downloads behave as they do for live jobs.

Only the analysis is different. A test job never runs the real analysis. It returns a
fixed fixture result instead:

| Product       | Fixture result                                                                                                 |
| ------------- | -------------------------------------------------------------------------------------------------------------- |
| Skin Analysis | every concern at a fixed score, and a fixed overall score and face quality, with small placeholder mask images |
| Makeup try-on | one PNG the size of your input, with a fixed stripe pattern                                                    |
| Hair try-on   | one PNG the size of your input, with a fixed stripe pattern                                                    |

The values and image sizes are the same every time. The image bytes can change between
releases, so compare values and shapes, not image bytes. Fixture images are placeholders,
not a realistic preview.

## What is not charged

A test job places no credit hold and moves no balance, whatever its outcome. Test usage
does not appear in your live usage figures. Test keys and test jobs belong to a separate
test environment of your account.

## Telling test from live

While test mode is on, every Job and Asset carries `livemode`:

```json
{
  "id": "job_...",
  "status": "succeeded",
  "livemode": false
}
```

`false` means a test object. `true` means a live one. Where test mode is off, the field is
left out.

Both SDKs expose it as `livemode` on a job and an asset, and it is `None`
(Python) or `null` (TypeScript) when absent. To check which kind of key a client holds without
making a request, read `client.is_test_mode` (Python) or `client.isTestMode`
(TypeScript).

Error responses carry no `livemode`; the key prefix you sent already tells you which
environment answered.

## Isolation

The two environments cannot see each other. A test key reading a live job id or asset id
gets `404`, as if the id did not exist, and a live key reading a test id does too. An
idempotency key used with a test key does not replay a live job.

## Choosing an outcome with a magic image

By default a test job succeeds. To get another outcome, upload one of the magic images
below as the job's input. The service recognises the exact bytes of each file. Download
the file and upload it unchanged; any re-encoding gives a different file and the default
outcome.

| Scenario              | Image                                                                      | SHA-256                                                            | What happens                                                                                                       |
| --------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `succeeded`           | [succeeded.png](../../assets/test-mode/succeeded.png)                      | `b924ad8177d49670aeecc725f8872c173e80391d9935d6daffe6e8b30cf145af` | The job succeeds with the fixture result. Any other image does the same.                                           |
| `input_unprocessable` | [input\_unprocessable.png](../../assets/test-mode/input_unprocessable.png) | `8435773607d1587beb9a3b1e72692e1edf138aeba62b9e374ae98d5597e23b02` | The job fails with `INPUT_UNPROCESSABLE`, as for a photo with no usable face.                                      |
| `processing_failed`   | [processing\_failed.png](../../assets/test-mode/processing_failed.png)     | `1b50094df98ac57e3f15bc6008119e927cee88c891d597dbb6a8a0a7ceb015bc` | The job fails with `PROCESSING_FAILED`.                                                                            |
| `slow`                | [slow.png](../../assets/test-mode/slow.png)                                | `065a7bdd98cfaa94a68a7aac3f9849af8a7ae445a81bb72444e359ddacd2b8ee` | The job stays `running` for a few seconds (5 by default), then succeeds. Use it to test your polling and timeouts. |
| `concern_unavailable` | [concern\_unavailable.png](../../assets/test-mode/concern_unavailable.png) | `8b28b8f21922a774af125dc4fc24a0c3df10ebe489aff9575e747b5b6ec90eaa` | Skin Analysis only: the job succeeds, but the `wrinkles` concern is reported as not measured.                      |

Each image is a plain 64×64 square of one colour. If a job has several inputs, the first
magic one decides the scenario.

A magic image only selects a scenario for a test key. Uploaded with a live key, it is an
ordinary image and runs the real analysis.

## Limits and retention

Test keys have their own rate-limit and queue buckets, so test traffic never uses up your
live limits. Test jobs and assets are kept for the same time as live ones and are deleted
on the same schedule.