# Idempotencia

> Cómo reintentar una escritura en la API de watsi sin aplicarla dos veces con el encabezado Idempotency-Key, qué rutas lo aceptan y cuánto tiempo se recuerda.

Source: https://docs.watsi.ai/api/idempotency

Si una escritura se corta a medio camino (la red se cae, tu proceso se reinicia), no sabes si watsi la aplicó. El encabezado `Idempotency-Key` te deja reintentarla sin miedo: si la primera ya se aplicó, el reintento te devuelve **su** resultado en lugar de escribir otra vez.

```bash
curl -X POST "https://api.watsi.ai/v1/actions/contacts/create" \
  -H "Authorization: Bearer $WATSI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed" \
  -d '{ "name": "María Pérez", "phone_number": "5215598765432" }'
```

## Qué rutas lo aceptan

Las escrituras que no son idempotentes por naturaleza. Las lecturas y las escrituras que dejan el mismo estado por más que las repitas (como cambiar el estado de una conversación) lo ignoran, porque ya son seguras de reintentar. Esta lista se genera del contrato:

| Método | Ruta | Alcances | Idempotency-Key |
| --- | --- | --- | --- |
| `POST` | `/v1/actions/agents/create` | `agents:write` | Sí |
| `POST` | `/v1/actions/agents/knowledge` | `knowledge:write` | Sí |
| `POST` | `/v1/actions/agents/knowledge/delete` | `knowledge:write` | Sí |
| `POST` | `/v1/actions/agents/update` | `agents:write` | Sí |
| `POST` | `/v1/actions/attachments/create-upload` | `agents:write` | Sí |
| `POST` | `/v1/actions/attachments/finalize` | `agents:write` | Sí |
| `POST` | `/v1/actions/attachments/import-url` | `agents:write` | Sí |
| `POST` | `/v1/actions/contacts/create` | `contacts:write` | Sí |
| `POST` | `/v1/actions/crm/tags` | `crm:write` | Sí |
| `POST` | `/v1/actions/crm/tags/remove` | `crm:write` | Sí |
| `POST` | `/v1/actions/lifecycle/attach-file` | `lifecycle:write` | Sí |
| `POST` | `/v1/actions/voice/config` | `agents:write` | Sí |

## Cómo se comporta

| Caso                                                 | Respuesta                                                      |
| ---------------------------------------------------- | -------------------------------------------------------------- |
| Primera solicitud con una clave                      | Se ejecuta normal                                              |
| Misma clave, mismo cuerpo, dentro de **30 segundos** | `200` con la respuesta guardada de la primera; no escribe nada |
| Misma clave, **otro** cuerpo, dentro de 30 segundos  | `409` `IDEMPOTENCY_KEY_REUSED`                                 |
| Misma clave pasados los 30 segundos                  | Se trata como una solicitud nueva                              |
| La primera solicitud **falló**                       | No se guardó nada: el reintento se ejecuta de nuevo            |

- La clave vale **por espacio de trabajo y por ruta**: la misma clave en `contacts/create` y en `crm/tags` son dos claves distintas.
- La comparación del cuerpo es por contenido: el orden de las propiedades del JSON no importa.
- La ventana de 30 segundos está pensada para el reintento inmediato de una solicitud que se cortó, no para deduplicar trabajos de horas. Para eso, guarda tú qué operaciones completaste.

## Buenas prácticas

- **Una clave por operación lógica**, generada antes del primer intento (un UUID v4 sirve) y reusada en cada reintento de esa operación.
- **Nunca la misma clave para dos operaciones distintas**: el segundo cuerpo recibe `409`.
- **Si recibes `409 IDEMPOTENCY_KEY_REUSED`**, es un error de tu código (dos operaciones compartieron clave), no algo que se arregle reintentando.

> **Note**

`contacts/create` además es un _upsert_: mandar el mismo teléfono, `wa_id` o correo actualiza el contacto existente en vez de duplicarlo, aun sin `Idempotency-Key` y fuera de la ventana. La respuesta lo dice con `"created": false`.
