> ## Documentation Index
> Fetch the complete documentation index at: https://help.pipelime.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency, deduplication and rate limits

> Retrying safely, how an existing lead is recognised, and what happens when you send too fast.

Idempotency stops a retried call from running twice, deduplication decides whether the person you're posting is one you already have, and the rate limit caps how often you can call at all: three different questions that only look related because you meet all three on the same call.

<div className="pl-availability">
  <div className="pl-availability__row">
    <div className="pl-availability__label">Where</div>
    <div className="pl-availability__value"><span className="pl-path">Settings<span className="pl-path__sep">→</span>API & Integrations</span></div>
  </div>

  <div className="pl-availability__row">
    <div className="pl-availability__label">Your role needs</div>
    <div className="pl-availability__value">Update access to Lead intake API <code>update-companies</code>. Admin have it by default.</div>
  </div>

  <div className="pl-availability__note">Authenticated by a workspace API key, not by a signed-in session. Creating a key needs the same permission as the rest of the API settings tab.</div>
</div>

| Mechanism     | Answers                             | Runs                                           | You control it with                             |
| ------------- | ----------------------------------- | ---------------------------------------------- | ----------------------------------------------- |
| Idempotency   | Did I already make this exact call? | Only when you send an `Idempotency-Key` header | The header itself                               |
| Deduplication | Do I already have this person?      | On every call, automatically                   | `on_duplicate` in the request body              |
| Rate limit    | Am I calling too often?             | On every call, automatically                   | Nothing to set: send fewer calls, or batch them |

## How it behaves

### Idempotency stops one call from running twice

Send an `Idempotency-Key` header on `POST /leads` or `POST /leads/batch`, and a retried call with the exact same body returns the stored response instead of running again. The reply carries an `Idempotency-Replayed: true` header so you can tell a fresh answer from a replayed one.

```bash cURL theme={"system"}
curl -X POST 'https://api.pipelime.ai/api/v1/leads' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H 'Idempotency-Key: 6a1f3b0e-1c9d-4b8a-9e2f-9d7b3a5c1e42' \
  -H 'Content-Type: application/json' \
  -d '{ "name": "Jane Doe", "email": "jane.doe@acme-demo.test", "tags": ["Inbound"] }'
```

Any non-empty string up to 255 characters works as the key. A UUID is the natural choice, but nothing checks the format. Send one longer than that and the call fails with `400` and `invalid_idempotency_key`. Your body is validated before the key is looked at, though, so a call that is both malformed and over-long comes back as a `422` about the body instead. The key is scoped to the API key that sent it: a second key reusing the exact same value, even inside the same workspace, is its own bucket and never collides with the first.

Reuse a key with a body that isn't identical to the first call's, and you get a `409` of its own, distinct from a duplicate-lead conflict. The whole request body is fingerprinted, not just the lead fields, so a second call under the same key with `dry_run` flipped counts as a different body and is rejected the same way.

<Note>
  Because the fingerprint covers the full body, you can't preview a lead with `dry_run: true` and then reuse the same `Idempotency-Key` to send it for real. The second call's body differs from the first and is rejected. Use one key per real attempt.
</Note>

Only a response below `400` is memoized. An error is never stored, so retrying after one genuinely runs again rather than replaying the failure.

<Warning>
  `POST /leads/batch` always answers `200` once it validates, per-row outcomes included, even when every row failed. See [POST /leads/batch](/en/developers/post-leads-batch). That means a batch call under an `Idempotency-Key` is memoized every time. Retry it and you get the identical per-row result back, not a fresh attempt at the rows that failed.
</Warning>

A replayed response skips the whole pipeline, not just the reply: no duplicate lookup, no tag creation, nothing queued a second time. A plain retry with no key usually lands on the same lead anyway: it matches the lead the first call created, on the same email, LinkedIn handle or phone, and the default `update` merges into it instead of creating a second row. What a key adds is that the retry changes nothing at all, rather than running that match again and merging into the same lead a second time. The stored response is kept for 24 hours; once it lapses, reusing the key runs the call for real again, as if it had never been sent.

### Deduplication recognizes a person you already have

Before either endpoint creates or updates a lead, it looks for an existing one in your workspace, checking the strongest identifier first: any of the person's own email addresses (`email` and `emails[]`, never the company's), then the profile handle inside `linkedin_url`, then a phone number. The match never looks outside the workspace the key belongs to, and a name is never part of it. Two people who happen to share one are two people, and merging them would be unrecoverable.

Phone matching compares trailing digits rather than the full string, so the same number spelled with and without a country code or a trunk prefix still matches; a number that reduces to fewer than 7 digits is skipped rather than risking a false match.

`on_duplicate` decides what happens once a match is found:

| `on_duplicate`     | What happens                                                                          |
| ------------------ | ------------------------------------------------------------------------------------- |
| `update` (default) | Merges the new payload into the lead you already have                                 |
| `skip`             | Leaves the existing lead as it is and reports the outcome as `skipped`                |
| `error`            | Nothing is created or updated: `POST /leads` answers `409` with the matched `lead_id` |

`on_duplicate` is a call-level switch, so on `POST /leads/batch` it applies to every row. A duplicate under `error` there fails only its own row: the call still answers `200`, and the failed row carries the matched `lead_id`.

`dry_run: true` runs this same check for real, inside a transaction that always rolls back. A preview tells you honestly whether a call would have matched an existing lead, without writing anything.

### Rate limits cap how often you can call

Every key gets a budget of 300 requests a minute, counted against the key itself, never the address the request came from. So an integration running on a shared or serverless host is measured on its own traffic alone.

A `POST /leads/batch` call counts as one request against that budget whatever it carries (up to 100 leads in a single call), so batching a list is how you move it through fast, rather than looping single `POST /leads` calls one lead at a time.

Cross the limit and further calls are rejected until the window clears; what comes back is on the [errors reference](/en/developers/errors).

## Limits

|                                     |                                                         |
| ----------------------------------- | ------------------------------------------------------- |
| Idempotency-Key max length          | 255 characters                                          |
| Idempotency replay window           | 24 hours                                                |
| What idempotency memoizes           | Only a response below `400`: an error is never replayed |
| Reusing a key with a different body | `409`, not a replay                                     |
| Deduplication scope                 | This workspace only                                     |
| Match order                         | Email, then LinkedIn profile, then phone                |
| Matched on name                     | Never                                                   |
| Rate limit                          | 300 requests a minute, per key                          |
| What a batch call counts as         | One request, whatever its `leads[]` length: up to 100   |

## Related

<CardGroup cols={2}>
  <Card title="POST /leads" icon="send" href="/en/developers/post-leads">
    The full request body and response envelope this page's rules apply to.
  </Card>

  <Card title="POST /leads/batch" icon="layers" href="/en/developers/post-leads-batch">
    Send many leads in one call, and read the outcome of each row on its own.
  </Card>

  <Card title="Errors reference" icon="circle-alert" href="/en/developers/errors">
    Every status the API returns and what to change before you retry.
  </Card>
</CardGroup>
