> For the complete documentation index, see [llms.txt](https://help.delpha.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.delpha.io/delpha-api-and-mcp/api/test-mode.md).

# Test mode

An API key is created in one of two modes — **live** or **test mode** — at **Developer → API keys**. A test-mode key calls the same host, the same operations and the same schemas as a live one, and is answered from a published table of canned results. The wire value of the mode is `sandbox`: that is what the response header and the webhook marker below carry.

* **The mode is fixed when the key is created.** It cannot be edited afterwards, and rotating a key preserves it. To change mode, create another key.
* **Nothing in the credential tells you its mode.** The `dlp_live_` / `dlp_test_` prefix records the environment that issued the key, never its mode. The two signals are the **Test mode** badge beside the key in the console and the response header below.
* **A test-mode call costs nothing.** It reserves no credits and settles none, so it never reaches your balance, your ledger or your usage figures — and it queries no provider, no datalake and no verification backend. The answer is computed from your request alone.
* **Every response the API renders to a test-mode call carries `X-Delpha-Mode: sandbox`**, success and error alike. A live response carries no such header — absence means live. The exceptions are the responses answered before the API sees the request (**401**, **403**, and the shared per-operation floor's **429**), which carry none of the per-request headers, and `GET /v1/pricing` (below).
* **`GET /v1/pricing` is the same for every caller.** It takes no credential, so it answers the live price table whatever key the request carries — test-mode, live or none — and its responses never carry `X-Delpha-Mode`.
* **A test-mode job is complete the moment it is accepted.** The submit is production's: **202** with a `job_id`, a `Location` header, `Retry-After`, and the same idempotency behaviour. The difference is that the first poll answers **200** with the result rather than the transient **202** `job_running`. A production polling loop therefore works unchanged against test mode; it simply ends on its first turn.
* **Every credentialed operation behaves the same way.** A test-mode key is refused by the **MCP server** alone, with `403 sandbox_key_unsupported` — use a live key there.
* **Webhooks fire as they do in production**, and a test-mode job's events carry `data.mode: "sandbox"` so a subscriber can tell them apart. Absence means live. One operation differs: `POST /v1/ultimate` emits its `sent` event and no completion event, because in production that completion is announced by the resolution engine a test-mode request never reaches. Its batch form is unaffected, and every other operation emits the full `sent` / `done` / `error` lifecycle. Poll the job for the result.
* **Latency is not representative.** A canned answer comes back immediately, while a live job takes as long as the work takes (each operation publishes its band in `x-typical-latency`). A timeout or poll interval measured against test mode therefore describes nothing about live behaviour.
* **Test-mode and live jobs are isolated.** A test-mode key polling a live job id — or a live key polling a test-mode one — is answered **404 `not_found`**, the same answer it would get for another team's job id.
* **Rate limits are the published ones, counted in a window of their own.** A test-mode loop cannot consume the allowance your production traffic is counted against, and it is not exempt from the limits themselves.

**Which answer you get.** One request field per use case decides the verdict. When the value of that field contains `invalid`, in any casing, the answer is that use case's **negative** verdict; anything else returns the **positive** answer, which describes Delpha's own public record. `POST /v1/{usecase}/batch` applies the same rule to each item — and every item succeeds, because the marker chooses a verdict rather than a failure, so test mode does not reproduce a partially failed batch.

| Operations                                                    | Field that decides                                                    | Carrying `invalid`                                              | Otherwise                                                         |
| ------------------------------------------------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------------------- |
| `POST /v1/email`                                              | the local part of `email` (an address with no `@` is always negative) | `label: "No"`, `email_status: "Invalid"`, `email_domain: null`. | `label: "OK"`, `email_status: "Valid"`, the domain resolved.      |
| `POST /v1/legal-id`                                           | `legal_id`                                                            | `label: "Unknown"`, `legal_id_type: null`, no suggestions.      | `label: "OK"`, the published identifier, one suggestion.          |
| `POST /v1/linkedin-profile`, `POST /v1/linkedin-company-page` | `url`                                                                 | `label: "No"`, no recommendations.                              | `label: "OK"`, one recommendation: Delpha's own company page.     |
| `POST /v1/name`                                               | `first_name` (else `last_name`)                                       | `label: "No"`, the submitted name echoed, no suggestions.       | `label: "OK"`, every score filled, one suggestion per part.       |
| `POST /v1/phone`                                              | `phone`                                                               | `normalized_value: null` (this result carries no `label`).      | `zone: "FR"`, normalised to the published number.                 |
| `POST /v1/website`                                            | `website`                                                             | `label: "No"`, no recommendations.                              | `label: "OK"`, the published site, one recommendation.            |
| `POST /v1/ultimate`                                           | `company_name`                                                        | the synthetic group with `confidence: 0`, no ownership chain.   | the synthetic group with `confidence: 0.95` and a two-step chain. |
| `GET /v1/company`                                             | `account_name` (else `search_str`)                                    | no companies, `total: 0`, `has_more: false`.                    | one company carrying its identifier and website.                  |
| `GET /v1/phone`                                               | `phone`                                                               | `valid: false`, nothing normalised.                             | `valid: true`, `zone: "FR"`, normalised to the published number.  |

Two details the table does not have room for. The ultimate-parent answers name a synthetic group under the reserved documentation domain rather than a real company. And both LinkedIn verifications answer with Delpha's own **company page**, including the profile operation, so that no individual's profile is part of a permanently published fixture; `job_title` is therefore always `null`.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://help.delpha.io/delpha-api-and-mcp/api/test-mode.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
