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

# Idempotency

> How to use idempotency keys to safely retry requests without duplicating data.

Write operations in the Vendaze API support idempotency via the `Idempotency-Key` header. This allows you to safely retry a request if you did not receive a response, without the risk of creating duplicate records.

## How to use it

Include the `Idempotency-Key` header with a unique string you generate:

```bash theme={null}
curl -X POST https://api.vendaze.com/v1/deals \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f8a9b2c-3d4e-5f60-a1b2-c3d4e5f60718" \
  -d '{
    "name": "Annual Acme Contract",
    "pipeline_id": "pipeline-uuid",
    "stage_id": "stage-uuid",
    "currency": "USD",
    "price_cts": 1200000
  }'
```

If you send the same request again with the same `Idempotency-Key`, the API returns the original response without processing it again.

## Key format

The key must be:

* A string of up to 255 characters
* Unique per operation (recommended: UUID v4)
* Generated by your system, not reused across different operations

```javascript theme={null}
import { randomUUID } from 'crypto';

// Generate a unique key per operation attempt
const idempotencyKey = randomUUID();
```

## Behavior by scenario

| Scenario                                          | Behavior                                            |
| ------------------------------------------------- | --------------------------------------------------- |
| First request                                     | Processes normally and stores the response.         |
| Repeated request with same key and same body      | Returns the original response without reprocessing. |
| Repeated request with same key and different body | Returns `422` with code `idempotency_conflict`.     |
| Repeated request after 24 hours                   | Key expired. Processes as a new request.            |

## Why PATCH and DELETE are naturally idempotent

`PATCH` and `DELETE` endpoints do not require the `Idempotency-Key` header because they are idempotent by definition:

* **PATCH:** applying the same update twice produces the same result. Setting `full_name` to `"Ana Costa"` a second time leaves the record unchanged.
* **DELETE:** deleting a record that has already been deleted returns `404`. The end state (record does not exist) is the same regardless of how many times you call it.

The `Idempotency-Key` header is only necessary for `POST` operations, where each call without a key would create a new record.

## Endpoints that support idempotency

The `Idempotency-Key` header is accepted on all write endpoints:

* `POST /v1/people`
* `POST /v1/companies`
* `POST /v1/deals`

## Recommended pattern for critical creations

```javascript theme={null}
import { randomUUID } from 'crypto';

async function createDeal(payload, accessToken) {
  // Generate the key once per logical operation, before the first attempt
  const idempotencyKey = randomUUID();

  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      const res = await fetch('https://api.vendaze.com/v1/deals', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': idempotencyKey,
        },
        body: JSON.stringify(payload),
      });

      if (res.ok) return res.json();

      // Do not retry on validation errors or client errors
      if (res.status >= 400 && res.status < 500) {
        const { error } = await res.json();
        throw new Error(`${error.code}: ${error.message}`);
      }

      // Retry on network failures or 5xx
      if (res.status >= 500) {
        await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
        continue;
      }
    } catch (err) {
      if (attempt === 2) throw err;
      await new Promise((r) => setTimeout(r, 1000 * Math.pow(2, attempt)));
    }
  }
}
```

## Tracking idempotency keys in your database

For operations where you need an audit trail or want to correlate your internal records with API responses, store the idempotency key alongside the created resource:

```sql theme={null}
CREATE TABLE deal_creations (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  idempotency_key UUID NOT NULL UNIQUE,
  vendaze_deal_id UUID,
  payload         JSONB NOT NULL,
  status          TEXT NOT NULL DEFAULT 'pending',
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  resolved_at     TIMESTAMPTZ
);
```

```javascript theme={null}
async function createDealWithTracking(payload, accessToken) {
  const idempotencyKey = randomUUID();

  // Record the attempt before sending
  await db.dealCreations.insert({
    idempotency_key: idempotencyKey,
    payload,
    status: 'pending',
  });

  try {
    const res = await fetch('https://api.vendaze.com/v1/deals', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey,
      },
      body: JSON.stringify(payload),
    });

    const deal = await res.json();

    await db.dealCreations.update(
      { idempotency_key: idempotencyKey },
      { status: 'success', vendaze_deal_id: deal.data.id, resolved_at: new Date() },
    );

    return deal;
  } catch (err) {
    await db.dealCreations.update(
      { idempotency_key: idempotencyKey },
      { status: 'failed', resolved_at: new Date() },
    );
    throw err;
  }
}
```

This pattern lets you recover from failures by looking up the `idempotency_key` in your database and checking whether the operation succeeded before retrying.

<Note>
  Idempotency keys persist for 24 hours. Always use a UUID v4 generated at the time of the
  operation. Never reuse keys across different operations.
</Note>

## Key conflict

If you send a previously used key with a different body, the API returns:

```json theme={null}
{
  "error": {
    "code": "idempotency_conflict",
    "message": "An idempotency key was reused with a different request body.",
    "request_id": "uuid"
  }
}
```

This protects against bugs where the same key is accidentally reused for two distinct operations.
