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

# Idempotencia

> Cómo reintentar una escritura sin duplicar nada.

Todas las escrituras exigen la cabecera `Idempotency-Key` con un identificador
único que **genera tu sistema**:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.ventry.es/v1/tickets \
    -H "Authorization: Bearer $VENTRY_KEY" \
    -H "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7" \
    -H "Content-Type: application/json" \
    -d '{ "ticket_type": "...", "valid_days": ["..."], "attendee": { "name": "Ada", "email": "ada@example.com" } }'
  ```

  ```js Node.js theme={null}
  await fetch("https://api.ventry.es/v1/tickets", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VENTRY_KEY}`,
      "Idempotency-Key": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ticket_type: "...",
      valid_days: ["..."],
      attendee: { name: "Ada", email: "ada@example.com" },
    }),
  });
  ```

  ```python Python theme={null}
  requests.post(
      "https://api.ventry.es/v1/tickets",
      headers={
          "Authorization": f"Bearer {os.environ['VENTRY_KEY']}",
          "Idempotency-Key": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      },
      json={
          "ticket_type": "...",
          "valid_days": ["..."],
          "attendee": {"name": "Ada", "email": "ada@example.com"},
      },
      timeout=30,
  )
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init("https://api.ventry.es/v1/tickets");
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer " . getenv("VENTRY_KEY"),
          "Idempotency-Key: 7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "Content-Type: application/json",
      ],
      CURLOPT_POSTFIELDS => json_encode([
          "ticket_type" => "...",
          "valid_days" => ["..."],
          "attendee" => ["name" => "Ada", "email" => "ada@example.com"],
      ]),
  ]);

  $response = json_decode(curl_exec($ch), true);
  curl_close($ch);
  ```
</CodeGroup>

## Por qué es obligatoria

Porque el caso malo es silencioso. Envías un lote de 300 entradas, se agota el
tiempo de espera y no sabes si llegó. Si reintentas sin clave de idempotencia y
sí había llegado, acabas con 600 entradas y nadie se entera hasta que el aforo no
cuadra el día del evento.

Con la cabecera, el reintento devuelve la respuesta original y no ejecuta nada.

## Comportamiento

<CardGroup cols={2}>
  <Card title="Misma clave, mismo cuerpo" icon="check">
    Devuelve la respuesta guardada, sin volver a ejecutar. La respuesta lleva la
    cabecera `Idempotent-Replayed: true`.
  </Card>

  <Card title="Misma clave, cuerpo distinto" icon="triangle-exclamation">
    `422 idempotency_key_reused`. Es una red de seguridad: significa que has
    reutilizado una clave por error.
  </Card>

  <Card title="Clave en vuelo" icon="clock">
    `409 idempotency_in_flight` si la operación se está procesando en ese
    instante. Espera un segundo y reintenta.
  </Card>

  <Card title="Claves distintas" icon="copy">
    Dos operaciones independientes. Enviar el mismo lote con dos claves distintas
    **sí** crea todo dos veces.
  </Card>
</CardGroup>

## Cómo generar la clave

Un UUID v4 vale. Lo importante es que sea **estable entre reintentos** de la
misma operación lógica y distinta entre operaciones distintas.

<CodeGroup>
  ```js Node.js theme={null}
  // Bien: la clave se genera una vez y se reutiliza en los reintentos.
  const idempotencyKey = crypto.randomUUID();
  await withRetries(() => createTickets(batch, idempotencyKey));

  // Mal: cada reintento genera una clave nueva y duplica el lote.
  await withRetries(() => createTickets(batch, crypto.randomUUID()));
  ```

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

  # Bien: la clave se genera una vez y se reutiliza en los reintentos.
  idempotency_key = str(uuid.uuid4())
  with_retries(lambda: create_tickets(batch, idempotency_key))

  # Mal: cada reintento genera una clave nueva y duplica el lote.
  with_retries(lambda: create_tickets(batch, str(uuid.uuid4())))
  ```

  ```php PHP theme={null}
  <?php
  // Bien: la clave se genera una vez y se reutiliza en los reintentos.
  $idempotencyKey = bin2hex(random_bytes(16));
  withRetries(fn () => createTickets($batch, $idempotencyKey));

  // Mal: cada reintento genera una clave nueva y duplica el lote.
  withRetries(fn () => createTickets($batch, bin2hex(random_bytes(16))));
  ```
</CodeGroup>

Si tus operaciones tienen ya un identificador propio —el número de pedido, por
ejemplo— úsalo. Es más robusto que un UUID en memoria, porque sobrevive a que se
caiga tu propio proceso:

<CodeGroup>
  ```js Node.js theme={null}
  const idempotencyKey = `pedido-${order.id}-entradas`;
  ```

  ```python Python theme={null}
  idempotency_key = f"pedido-{order.id}-entradas"
  ```

  ```php PHP theme={null}
  $idempotencyKey = "pedido-{$order->id}-entradas";
  ```
</CodeGroup>

## Alcance

La clave es tuya: dos integradores distintos pueden usar el mismo valor sin
pisarse, y la misma clave en dos endpoints distintos son dos operaciones
distintas.

Las operaciones completadas se recuerdan **7 días**. Pasado ese plazo, la misma
clave se trata como una operación nueva.

## El caso de la acreditación

`POST /tickets/{code}/check-in` usa la `Idempotency-Key` como identificador del
escaneo. Eso lo hace especialmente seguro de reintentar: si la red se cortó
justo después de canjear la entrada, el reintento devuelve **la misma pulsera**
en lugar de emitir una segunda.

Genera una clave por escaneo físico y consérvala mientras dure el reintento:

<CodeGroup>
  ```js Node.js theme={null}
  const scanId = crypto.randomUUID();  // una vez, al leer el QR

  await checkIn(ticketCode, wristbandNfc, scanId);  // reintentable sin miedo
  ```

  ```python Python theme={null}
  scan_id = str(uuid.uuid4())  # una vez, al leer el QR

  check_in(ticket_code, wristband_nfc, scan_id)  # reintentable sin miedo
  ```

  ```php PHP theme={null}
  $scanId = bin2hex(random_bytes(16));  // una vez, al leer el QR

  checkIn($ticketCode, $wristbandNfc, $scanId);  // reintentable sin miedo
  ```
</CodeGroup>
