> ## Documentation index
> Fetch the complete documentation index at: https://docs.leadity.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Create validation

> Validate a list of phone numbers or email addresses you supply.

Takes credits: checks a list of phone numbers or email addresses you supply, answering a verdict per item (the line type of a number, whether an address can receive mail), and holds the price of the whole list up front. Price it first with preview_validation, which is free and answers how many items survive parsing and what they cost in credits. maxCredits must cover the hold. An answer that tells nothing (unknown or invalid for a number, unknown or catch_all for an address) costs nothing, and its credits come back when the run settles. The call answers at once: watch the run with get_validation every 30 to 60 seconds, never faster, until its status is final; a few thousand items take a minute or two. Read the answers with list_validation_items or download_validation. Needs a key with the spend scope.

`POST https://api.leadity.io/v1/validations`

- Scope: `spend`
- Spends credits: yes
- MCP tool: `create_validation`

## Headers

- `Authorization`, string, required. Bearer, a space, then an API key: Bearer lk_api_... An MCP key is refused here with wrong_key_kind.
- `Idempotency-Key`, string, required. Your own key for this one purchase. Send the same key again to retry it safely: the retry replays the first answer instead of charging twice, and carries Idempotent-Replayed: true. The same key on a different request answers idempotency_conflict, and a spend without one answers idempotency_key_required. 16 to 64 characters, matches ^[A-Za-z0-9_-]+$.
- `Content-Type`, string, required. application/json

## Body parameters

- `kind`, string, required. phone to classify line types, email to check deliverability. One of: `phone`, `email`.
- `items`, string[], optional. The list as an array, one phone number or address per item: 1 to 50,000 items of at most 254 characters, and at most 8 MB joined one per line, as raw. Send items or raw, not both. 1 to 50,000 items, each 1 to 254 characters.
- `raw`, string, optional. The list as pasted text, one item per line or separated by commas, at most 8 MB (8,388,608 bytes of UTF-8). Send items or raw, not both. 1 to 8,388,608 characters.
- `region`, string, optional. For a phone list, the country a number written without its country code is read under: US (also Canada), GB or AU. Default US. Ignored for addresses. One of: `US`, `GB`, `AU`.
- `title`, string, optional. A name for the run. Left out, one is derived from its kind and date. 1 to 100 characters.
- `maxCredits`, integer, required. The most this call may take, in whole credits. If the price is higher nothing is charged and the call answers quote_exceeded. At most 100,000,000, greater than 0.

## Example request

```bash cURL
curl -X POST "https://api.leadity.io/v1/validations" \
  -H "Authorization: Bearer $LEADITY_API_KEY" \
  -H "Idempotency-Key: order-2026-10-04-0001" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "email",
    "items": [
      "owner@lonestarroofing.example",
      "info@acme-roofing.example"
    ],
    "maxCredits": 1
  }'
```

```javascript Node.js
// Make the key once per purchase and reuse it on every retry.
const idempotencyKey = crypto.randomUUID();

const response = await fetch("https://api.leadity.io/v1/validations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.LEADITY_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "kind": "email",
    "items": [
      "owner@lonestarroofing.example",
      "info@acme-roofing.example"
    ],
    "maxCredits": 1
  }),
});

const data = await response.json();
console.log(data);
```

```python Python
import os
import uuid

import requests

# Make the key once per purchase and reuse it on every retry.
idempotency_key = str(uuid.uuid4())

response = requests.post(
    "https://api.leadity.io/v1/validations",
    headers={
        "Authorization": f"Bearer {os.environ['LEADITY_API_KEY']}",
        "Idempotency-Key": idempotency_key,
    },
    json={
        "kind": "email",
        "items": ["owner@lonestarroofing.example", "info@acme-roofing.example"],
        "maxCredits": 1,
    },
)

print(response.json())
```

## Response

- `validation`, object, required.
  - `id`, string, required. The run's id.
  - `title`, string, required. The run's name.
  - `kind`, string, required. phone to classify line types, email to check deliverability. One of: `phone`, `email`.
  - `region`, string, required. The region a phone list was read under. US on an email run. One of: `US`, `GB`, `AU`.
  - `status`, string, required. queued and running are live; completed, failed and canceled are final. A failed run bills only what was answered. One of: `queued`, `running`, `completed`, `failed`, `canceled`.
  - `source`, string, required. Where this was started: dashboard (the Leadity app), api (an API key), mcp (an MCP key), stress or import (Leadity's own tools). One of: `dashboard`, `api`, `mcp`, `stress`, `import`.
  - `submitted`, integer, required. Lines or items submitted.
  - `rejected`, integer, required. Items the parser refused as not a phone number or address.
  - `duplicates`, integer, required. Repeats collapsed to one.
  - `total`, integer, required. Items checked: submitted minus rejected and duplicates.
  - `processed`, integer, required. Items answered so far.
  - `billable`, integer, required. Answers that cost credits so far. An answer that tells nothing costs nothing.
  - `reservedCredits`, integer, required. Credits held when the run was created.
  - `creditsUsed`, integer | null, required. Credits kept, set when the run settles; the rest was refunded. Null before.
  - `creditsPerItem`, number, required. The price of one billable answer.
  - `createdAt`, string, required. When the run was created. An ISO 8601 timestamp in UTC.
  - `startedAt`, string | null, required. When the run started. An ISO 8601 timestamp in UTC, or null.
  - `settledAt`, string | null, required. When the run settled. An ISO 8601 timestamp in UTC, or null.
- `truncated`, boolean, required. True when the list held more than the most one run takes, and only that many are checked.
- `rejectedSample`, string[], required. Up to eight refused lines.
- `replayed`, boolean, required. True when this idempotency key had already created the run: nothing new was charged, and this is that run.

## Example response

```json 201
{
  "validation": {
    "id": "Wq5nB8kT3m",
    "title": "Email validation | Oct 4, 2:12 PM",
    "kind": "email",
    "region": "US",
    "status": "queued",
    "source": "api",
    "submitted": 2,
    "rejected": 0,
    "duplicates": 0,
    "total": 2,
    "processed": 0,
    "billable": 0,
    "reservedCredits": 1,
    "creditsUsed": null,
    "creditsPerItem": 0.2,
    "createdAt": "2026-10-04T14:12:09.000Z",
    "startedAt": null,
    "settledAt": null
  },
  "truncated": false,
  "rejectedSample": [],
  "replayed": false
}
```

## Errors

Every refusal answers `{ error, message }` with the status below. The full list is on the [Errors](https://docs.leadity.io/errors) page.

| Code | Status | Meaning | Extra fields |
| --- | --- | --- | --- |
| `unauthorized` | 401 | The API key is missing, malformed, revoked or expired. | - |
| `wrong_key_kind` | 401 | This key opens the other surface: an API key works on the HTTP API, an MCP key on the MCP server. | - |
| `insufficient_scope` | 403 | The key does not carry the scope this operation needs. | `requiredScope`: The scope the operation needs: read, write or spend. |
| `account_suspended` | 403 | The account is suspended. | - |
| `email_not_verified` | 403 | The account's email address is not verified yet. | - |
| `invalid_body` | 400 | The request does not match the operation's schema. | `issues`: The problems found, each with path (the field as a dotted path, led by the part of the request it travels in: path, query or body, such as body.items.1) and message (what is wrong there). |
| `paywall_required` | 402 | This operation needs a purchase on the account first. | - |
| `insufficient_credits` | 402 | The balance does not cover the price. Nothing was charged. | `required`: On create_validation: the credits the list holds. |
| `quote_exceeded` | 409 | The price is above the maxCredits the call allowed. Nothing was charged. | `credits`: What the call would have taken: the whole hold on an order or a list, the price or the hold on an upgrade. `maxCredits`: The ceiling the call sent. `reviewsReservedCredits`: On create_scrape: the part of credits held for review scraping. `count`: On purchase_phone_reveal, when the count was the cause: how many numbers the reveal would name. `maxNumbers`: On purchase_phone_reveal, when the count was the cause: the maxNumbers the call sent. |
| `idempotency_key_required` | 400 | A spending operation must carry an Idempotency-Key header (16 to 64 characters of A-Z, a-z, 0-9, _ and -). | - |
| `invalid_idempotency_key` | 400 | The Idempotency-Key must be 16 to 64 characters of A-Z, a-z, 0-9, _ and -. | - |
| `idempotency_conflict` | 409 | This Idempotency-Key was already used for a different request. | - |
| `rate_limited` | 429 | Too many requests. Wait for the Retry-After seconds and try again. | `retryAfterSeconds`: The seconds to wait, the same number as the Retry-After header. |
| `try_again` | 503 | Leadity could not answer just now. Retry in a moment, with the same Idempotency-Key on a spending call. | `retryAfterSeconds`: When a request with this Idempotency-Key is still being processed: the seconds to wait before retrying with the same key, as the Retry-After header says. |
| `internal_error` | 500 | Something failed on our side. Nothing was charged twice. | `body`: On the MCP server, when Leadity answered in a shape the tool does not publish: that answer as it came. |
