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

# Idempotency

> Avoid duplicate charges on retries with the Idempotency-Key header and the externalRequestId reconciliation key.

Creating a charge is not an idempotent operation by nature: two calls with the same body create two distinct resources. So that you can **retry without duplicating** and **reconcile** your operations against those of TapiPay, the Facade API follows the header-based idempotency pattern (the same approach as Stripe).

## How it works

Send the **`Idempotency-Key`** header in your creation requests. It is **optional**, and your `body` stays clean: control information is not mixed with the resource data.

```bash theme={null}
curl --request POST \
  --url 'https://tapipay-facade.dev.tapila.cloud/debts' \
  --header 'x-api-key: YOUR_API_KEY' \
  --header 'x-authorization-token: YOUR_TAPI_TOKEN' \
  --header 'Idempotency-Key: INV-2026-001' \
  --header 'Content-Type: application/json' \
  --data '{ "externalClientId": "CLI-00042", "amount": 1500.00, "dueDate": "2026-07-01" }'
```

The response **always** includes the `externalRequestId` field, whether or not you sent the key:

* **If you sent `Idempotency-Key`**: `externalRequestId` is exactly the value you sent.
* **If you did not send it**: the backend generates an `externalRequestId` and returns it to you, so that you can use it in future queries and reconciliation.

<CodeGroup>
  ```json With Idempotency-Key theme={null}
  {
    "data": {
      "debtId": "debt_1xY7zR4p",
      "externalRequestId": "INV-2026-001",
      "status": "PENDING"
    },
    "requestId": "req_a1b2c3d4"
  }
  ```

  ```json Without Idempotency-Key theme={null}
  {
    "data": {
      "debtId": "debt_9aL3kP2m",
      "externalRequestId": "9f1c2e7a-4b21-4d8e-9c3a-1f7b2e9d4c5a",
      "status": "PENDING"
    },
    "requestId": "req_a1b2c3d4"
  }
  ```
</CodeGroup>

## Permanent deduplication

The `externalRequestId` is a **permanent unique key** within your organization: there is a uniqueness constraint with no expiration (no TTL or time window).

<Warning>
  Retrying with an already-used `Idempotency-Key` does **not** return the original resource: it returns a **`409 RESOURCE_CONFLICT`** error. The resource is not duplicated and you receive an explicit signal that the key was already taken.
</Warning>

```json 409 RESOURCE_CONFLICT theme={null}
{
  "error": {
    "code": "RESOURCE_CONFLICT",
    "message": "externalRequestId already exists"
  },
  "requestId": "req_3f8a1c9e2b"
}
```

This is why the key must be **unique per operation intent**: use a distinct key for each charge you want to create, and reuse the same one only when you are retrying exactly the same operation after a timeout or a network error.

## Dual function: idempotency and reconciliation

The `externalRequestId` serves two roles at once:

<CardGroup cols={2}>
  <Card title="Idempotency" icon="shield-check">
    Protects against duplicates when you retry a request that failed or expired.
  </Card>

  <Card title="Reconciliation" icon="scale-balanced">
    It is the external key of the debt. You can filter and list by it: `GET /debts?externalRequestId=...`.
  </Card>
</CardGroup>

## Where each key applies

Each creation resource has its own external key. Only debts use the header; subscriptions and payment links carry their key in the body.

| Resource     | External key             | Where it is sent                    |
| ------------ | ------------------------ | ----------------------------------- |
| Debt         | `externalRequestId`      | `Idempotency-Key` header (optional) |
| Subscription | `externalSubscriptionId` | Request body                        |
| Payment link | `externalPaymentLinkId`  | Request body (optional)             |

<Note>
  Idempotency applies **per organization**. The same `Idempotency-Key` in two distinct organizations does not interfere with each other.
</Note>
