# Idempotency-Key

> Reintenta peticiones no seguras sin riesgo con la cabecera opcional Idempotency-Key.

Todo método **no seguro** (`POST`, `PUT`, `PATCH`, `DELETE`) en las operaciones de `/api/v1` **con contexto de organización** — es decir, las que se ejecutan dentro de una organización activa, como proyectos, defectos, casos de prueba, ejecuciones y planes — acepta una cabecera OPCIONAL `Idempotency-Key`. Envía la misma clave en un reintento y Probara reproduce la respuesta original en lugar de volver a ejecutar la mutación — útil para reintentos de red, timeouts del cliente y esos dobles clics donde no sabes si la petición llegó a enviarse. Las peticiones `GET`/`HEAD` ignoran la cabecera por completo.

Los endpoints de autenticación (`/api/v1/auth/*`), perfil de usuario (`/api/v1/profile`, `/api/v1/profile/avatar`) y subida de avatar de proyecto (`/api/v1/projects/{projectId}/avatar`) **no** admiten esta cabecera — se ignora en silencio.

Las subidas de archivos (cualquier petición con cuerpo `multipart/form-data`, como la preparación de adjuntos o la subida de evidencias de un defecto) también ignoran la cabecera: no se registra ni se reproduce nada. Solo las peticiones de preparación se pueden reintentar sin clave de forma segura, porque un archivo preparado que ninguna petición posterior referencia caduca por sí solo. La subida de evidencias de un defecto no es una preparación: un reintento puede guardar los mismos archivos dos veces, así que revisa la lista de adjuntos del defecto antes de reintentarla.

Las claves están limitadas por organización y por el miembro que actúa: el mismo valor de clave usado por otra organización, o por otro miembro dentro de la misma organización, se trata como una clave independiente y nunca reproduce una respuesta entre esos límites.

## Formato de la clave

- No vacía
- No mayor de **255 caracteres ASCII visibles** (sin espacios en blanco, caracteres de control ni puntos de código fuera de ASCII)

Una clave inválida responde `400` y el handler nunca se ejecuta.

## Uso

```bash
# Primer intento — se ejecuta con normalidad y la respuesta queda registrada bajo la clave.
curl -sS -X POST \
  -H "Authorization: Bearer $PROBARA_API_TOKEN" \
  -H "Idempotency-Key: 8f14e45f-ceea-4a3c-b9a8-1a6b3d2f0e91" \
  -H "Content-Type: application/json" \
  -d '{"name":"Flujo de checkout"}' \
  "https://app.probara.net/api/v1/projects"
```

```http
HTTP/1.1 201 Created
Content-Type: application/json

{ "ulid": "01J...PROJ", "name": "Flujo de checkout", ... }
```

Reintentar la petición idéntica con la misma clave y el mismo cuerpo dentro de la ventana de retención devuelve el mismo estado y cuerpo — sigue siendo `01J...PROJ`, sin un segundo proyecto creado — con una cabecera adicional en la respuesta:

```http
HTTP/1.1 201 Created
Idempotency-Replayed: true
Content-Type: application/json

{ "ulid": "01J...PROJ", "name": "Flujo de checkout", ... }
```

## Semántica de la respuesta

| Situación                                                         | Respuesta                                                                                                              |
| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Primera petición con una clave nunca vista                        | Se ejecuta con normalidad; la respuesta se registra para reproducirla (JSON, estado `< 500`, sin ser `204` ni binaria) |
| Reintento idéntico dentro de la ventana de retención              | Mismo estado y cuerpo que el original, más `Idempotency-Replayed: true`                                                |
| Llega un duplicado mientras la primera petición sigue en curso    | `409` con el envoltorio de error estándar (`code: "conflict"`) y una cabecera `Retry-After`                            |
| Se reutiliza la misma clave con un método, ruta o cuerpo distinto | `422` con `code: "validation_failed"`; no se ejecuta nada                                                              |
| Clave inválida (vacía o de más de 255 caracteres ASCII visibles)  | `400`; no se ejecuta nada                                                                                              |
| La primera petición falla                                         | Se libera la reserva de la clave; un reintento posterior con la misma clave se ejecuta con normalidad                  |
| La respuesta es `204`, no es JSON o es binaria                    | Pasa sin registrarse — un reintento con la misma clave vuelve a ejecutar el handler                                    |

## Retención

Los registros de clave se conservan durante **24 horas**. Pasada esa ventana, la misma clave se comporta como una clave nueva, nunca vista: la petición idéntica vuelve a ejecutar el handler y `Idempotency-Replayed` no aparece en esa respuesta.

## Errores

| Estado | Código              | Significado                                                                      |
| ------ | ------------------- | -------------------------------------------------------------------------------- |
| 400    | `validation_failed` | `Idempotency-Key` está vacía o supera los 255 caracteres ASCII visibles          |
| 409    | `conflict`          | Hay una petición en curso con la misma clave; incluye una cabecera `Retry-After` |
| 422    | `validation_failed` | La clave ya se usó con un método, ruta o cuerpo de petición distinto             |

## Relacionado

- [Autenticación API](https://docs.probara.net/es/developers/authentication/) — tokens Bearer y sesión
- [Referencia API](https://docs.probara.net/es/developers/api-reference/) — paginación, errores, PATCH consolidado
