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

# Idempotencia

> Evita cobros duplicados en reintentos con el header Idempotency-Key y la llave de conciliación externalRequestId.

Crear un cobro no es una operación idempotente por naturaleza: dos llamadas con el mismo cuerpo crean dos recursos distintos. Para que puedas **reintentar sin duplicar** y **conciliar** tus operaciones contra las de TapiPay, la Facade API sigue el patrón de idempotencia por header (el mismo enfoque que Stripe).

## Cómo funciona

Envía el header **`Idempotency-Key`** en tus peticiones de creación. Es **opcional**, y tu cuerpo (`body`) queda limpio: la información de control no se mezcla con los datos del recurso.

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

La respuesta **siempre** incluye el campo `externalRequestId`, hayas enviado o no la key:

* **Si enviaste `Idempotency-Key`**: `externalRequestId` es exactamente el valor que mandaste.
* **Si no la enviaste**: el backend genera un `externalRequestId` y te lo devuelve, para que lo uses en consultas y conciliación futuras.

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

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

## Deduplicación permanente

El `externalRequestId` es una **llave única permanente** dentro de tu organización: existe una restricción de unicidad sin vencimiento (no hay TTL ni ventana temporal).

<Warning>
  Reintentar con una `Idempotency-Key` ya usada **no** devuelve el recurso original: devuelve un error **`409 RESOURCE_CONFLICT`**. El recurso no se duplica y recibes una señal explícita de que la key ya estaba ocupada.
</Warning>

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

Por eso la key debe ser **única por intención de operación**: usa una key distinta para cada cobro que quieras crear, y reusa la misma solo cuando estás reintentando exactamente la misma operación tras un timeout o un error de red.

## Doble función: idempotencia y conciliación

El `externalRequestId` cumple dos roles a la vez:

<CardGroup cols={2}>
  <Card title="Idempotencia" icon="shield-check">
    Protege contra duplicados cuando reintentas una petición que falló o expiró.
  </Card>

  <Card title="Conciliación" icon="scale-balanced">
    Es la llave externa de la deuda. Puedes filtrar y listar por ella: `GET /debts?externalRequestId=...`.
  </Card>
</CardGroup>

## Dónde aplica cada llave

Cada recurso de creación tiene su propia llave externa. Solo las deudas usan el header; las suscripciones y las ligas de pago llevan su llave en el cuerpo.

| Recurso      | Llave externa            | Dónde se envía                      |
| ------------ | ------------------------ | ----------------------------------- |
| Deuda        | `externalRequestId`      | Header `Idempotency-Key` (opcional) |
| Suscripción  | `externalSubscriptionId` | Cuerpo del request                  |
| Liga de pago | `externalPaymentLinkId`  | Cuerpo del request (opcional)       |

<Note>
  La idempotencia se aplica **por organización**. La misma `Idempotency-Key` en dos organizaciones distintas no interfiere entre sí.
</Note>
