# Test mode

> Every test mode endpoint, generated from the API's own schemas.

Base URL `https://pidgeon.ai/api/v1`. Every request carries `Authorization: Bearer pgn_live_…`. The machine-readable version of this page is https://pidgeon.ai/openapi.json.

## POST /test/inbound

Simulate an inbound message (test keys only)

Delivers a message to one of this **test key**’s addresses as though it had arrived from outside, through the real inbound pipeline — routing, sender rules, spam, the Firewall, threading, `message.received` and its webhooks — marked `test`. Pass `in_reply_to` with a message id to have it arrive as a reply in that thread, which is how a test plays the customer answering your agent.

A live key gets `403`. The address must be one the key reaches at `send`. Nothing is sent to `from`, and the message counts against nothing on your plan.

**Parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | no | Any string unique to this attempt, reused on its retries. A repeat with the same key and the same request returns the first answer with `Idempotent-Replay: true` and does nothing — no second email. The same key with a different request is a `409`. Kept for 24 hours; a failure is not kept, so a retry after one runs. |

**Body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | email | yes | One of the addresses this test key reaches, at `send`. |
| `from` | email | yes | Who it is from. Any address: nothing is sent to it. |
| `from_name` | string | no |  |
| `subject` | string | no | Left off a reply, where the thread already has one. |
| `text` | string | no | The plain-text body. Send this even when you send `html` — some clients only read it. |
| `html` | string | no | The rich body. Sanitised on the way in; scripts and remote content are removed. |
| `data` | object | no | Typed data: structured JSON sent beside the text, for software at the other end to read as fields. Travels as its own part; it is never mixed into the text. |
| `in_reply_to` | uuid \| null | no | A message id: this arrives as a reply to it, in its thread. |

**Responses**

- `201` Where it landed.
- `400` The request is not valid. `error.field` names the input at fault when there is one.
- `401` No API key, or one that is not valid.
- `403` Refused. `error.code` says why: `forbidden` when this key may not do this or the account is on hold, `abuse_blocked` when a limit says no.
- `404` No such resource, or one this key cannot reach. The two answer the same, so treat a 404 as “not available to you” rather than “does not exist”.
- `409` This `Idempotency-Key` was used with a different request, or that request is still running. Nothing was sent by this call.
- `429` Rate limited. `X-RateLimit-Reset` says when to retry.
- `500` Something went wrong on our side. Retry; if it keeps happening, tell us.
- `503` Something this needs is not answering right now. Retry in a moment.
