# Test mode

Integrate end to end with mh_test_ keys. Sample outputs, real webhooks, no charges, and switches to force failures.

A `mh_test_` key runs the whole API against a simulated pipeline. Use it to build and test your integration, including while your account is still in review.

## What is real and what is simulated

| | In test mode |
|---|---|
| Validation | Real: auth, scopes, body, idempotency, catalogue, file checks, queue cap |
| Timeline | Simulated: `queued` to `processing` within a few seconds, then `succeeded` about 20 seconds later. A storyboard runs one simulated job per stage, so it takes a few times longer |
| Outputs | Sample clips and images for the variant, served from signed URLs like live outputs |
| Webhooks | Real, signed with the test secret, retried on the same schedule |
| Money | None. Nothing is held or charged, whatever your balance |
| Data | Separate. Test keys never see live generations or files, and live keys never see test ones |

## Forcing outcomes

Put `test_outcome` in `metadata` to exercise your error handling:

| `metadata.test_outcome` | Result |
|---|---|
| `failed` | Ends `failed` with `error.code: "gpu_failure"` |
| `timeout` | Ends `failed` with `error.code: "timeout"`, about a minute after create |
| `slow` | Takes ten times longer than usual before succeeding |
| `insufficient_balance` | The create answers `402 insufficient_balance` |

```sh
curl https://api.moan.hot/v1/generations \
  -H "Authorization: Bearer $MOANHOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "effect": "EFFECT_SLUG",
    "variant": { "duration": "5s", "resolution": "640p", "audio": false, "prep": false },
    "inputs": { "image": "file_..." },
    "metadata": { "test_outcome": "failed" }
  }'
```

The `metadata` key has no effect on live keys.

[This sample image](/docs/test-mode/input-rejected.png) always ends in `input_rejected`, so you can test that path too. Upload it with `POST /files` (or pass its URL) and create a generation with it: the create succeeds, and the generation ends `failed` a few seconds later.

## Going live

Switching is a key swap. Use the same code with a `mh_live_` key once your account is approved and your balance is topped up. Remember that live webhooks use the live signing secret.
