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

# Idempotency

> Retry a calculation after a timeout without duplicating your history

`POST /v1/payroll/calculate` and `POST /v1/payroll/batch` accept an `Idempotency-Key`
header. A retry after a timeout returns the **original stored calculation** instead of
writing a second one.

This matters more now that calculations are recorded. Without it, a network timeout on a
500-employee batch leaves you unable to tell whether you have one history or two.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.clevis.dev/v1/payroll/calculate \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Idempotency-Key: nomina-co-2026-09-emp_001" \
    -H "Content-Type: application/json" \
    -d '{
      "country": "CO",
      "scheme": "ordinario",
      "year": 2026,
      "period": {
        "type": "monthly",
        "start_date": "2026-09-01",
        "end_date": "2026-09-30",
        "days": 30
      },
      "employee": {
        "id": "emp_001",
        "daily_salary": 100000,
        "monthly_salary": 3000000,
        "risk_class_rate": 0.00522
      },
      "employer": { "id": "employer_001" }
    }'
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.clevis.dev/v1/payroll/calculate",
      headers={
          "Authorization": "Bearer YOUR_API_KEY",
          "Idempotency-Key": "nomina-co-2026-09-emp_001",
      },
      json=payload,
  )
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.clevis.dev/v1/payroll/calculate", {
    method: "POST",
    headers: {
      Authorization: "Bearer YOUR_API_KEY",
      "Idempotency-Key": "nomina-co-2026-09-emp_001",
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
  ```
</CodeGroup>

## Choosing a key

A good key is **deterministic**, so a retry produces the same one, and **unique per
logical calculation**, so unrelated requests do not collide. Deriving it from the payroll
run gives you both:

```
nomina-{country}-{year}-{month}-{employee_id}
```

## Scoping

Idempotency keys are scoped to your **`client_id`**, not to the API key you presented.
Rotating a key mid-retry does not turn a replay into a new calculation, and it does not
hide the original from you. See [Authentication](/authentication).

## Retrying a 503

`503 PERSISTENCE_UNAVAILABLE` means the calculation **was not recorded**, so no `id` and
no amounts came back. Retry with the **same** `Idempotency-Key`: if an earlier attempt did
land, you get that one rather than a duplicate.

<Note>
  This page covers idempotency for *calculations*. Payout creation has its own semantics,
  including `external_id` and the `409` conflicts — see
  [Payouts idempotency](/payouts/idempotency).
</Note>
