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

# Idempotency

> Every call that takes credits carries a key of yours, so a retry replays the first answer instead of buying twice.

A network can drop the answer to a request that succeeded. Without protection, the natural retry would place a second order and take the credits twice. Every operation that takes credits therefore requires an `Idempotency-Key` header.

```bash
curl -X POST "https://api.leadity.io/v1/scrapes" \
  -H "Authorization: Bearer $LEADITY_API_KEY" \
  -H "Idempotency-Key: tx-roofers-2026-10-04" \
  -H "Content-Type: application/json" \
  -d @order.json
```

## The rules

- **One key per purchase.** Make a new key for each order you mean to place, and keep it with that order.
- **The same key for every retry.** Sending the same key with the same request again replays the first answer: nothing is charged a second time, and the answer says so with the header `Idempotent-Replayed: true`.
- **A key is tied to its request.** The same key with a different body is refused with `409 idempotency_conflict`, so a key can never buy something you did not ask for. Do not retry a conflict: send the new request under a new key.
- **A refusal leaves the key free.** A call refused for any reason, such as `insufficient_credits` or `quote_exceeded`, took nothing, so fix the cause and send it again under the same key, changed or not.
- **The shape is fixed.** 16 to 64 characters of letters, digits, `_` and `-`. A UUID works. A key outside that shape is refused with `invalid_idempotency_key` rather than ignored, because believing you are protected when you are not is the state the key exists to prevent.
- **A spend without one is refused** with `idempotency_key_required`. Reads never need a key.

## What a retry answers

| The first request | A retry with the same key and request |
| --- | --- |
| Placed a scrape or created a validation run | `200` with that scrape or run, `"replayed": true` in the body and `Idempotent-Replayed: true` |
| Bought an upgrade to a finished scrape | The first answer again, verbatim, with `Idempotent-Replayed: true` |
| Is still being processed | `503 try_again` with `Retry-After: 2`: wait that long and retry with the same key, which then reads the first answer |
| Was refused | Runs again, because the refusal took nothing |

A key names its request for 24 hours. After that, a retry of an order or a list still answers with the scrape or run its key made, and a retried upgrade meets the upgrade's own state, such as `already_purchased`, rather than buying it twice.

```python Python
import os
import uuid

import requests

# Make the key once, before the first attempt.
key = str(uuid.uuid4())

for attempt in range(3):
    try:
        response = requests.post(
            "https://api.leadity.io/v1/scrapes",
            headers={
                "Authorization": f"Bearer {os.environ['LEADITY_API_KEY']}",
                "Idempotency-Key": key,
            },
            json=order,
            timeout=30,
        )
        break
    except requests.ConnectionError:
        continue  # Same key on every attempt: at most one order is placed.
```

## On the MCP server

An MCP tool takes no idempotency key: the server makes one from the call itself. The same MCP key and the same tool with the same arguments inside the same ten-minute window give the same idempotency key, so a model that retries a spend replays the first answer. A deliberately identical second order inside that window also replays, so change something, such as the title or the lead count, to buy again.

The windows are fixed slots of the clock, not ten minutes from each call, so a retry that falls just past a slot's end makes a new key and is a new purchase. Its `maxCredits` still caps what it can take, and a second upgrade on the same scrape is refused by the scrape's own state.
