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

# Idempotencia

> Deduplica envíos con idempotencyKey y ten en cuenta que el envío individual y el lote tratan los duplicados de forma distinta.

`idempotencyKey` es una cadena opcional que aporta el cliente y hace que un envío se deduplique. Envía la misma
clave dos veces y el segundo envío se resuelve en la tarea que creó el primero, sin encolar trabajo nuevo.

```json theme={null}
{
  "taskType": "GEMINI",
  "idempotencyKey": "job-42:prompt-7:gemini",
  "payload": { "prompt": "best wireless earbuds 2026" }
}
```

Si la omites **no hay deduplicación**: cada envío crea una tarea nueva. Es una elección válida para consultas
puntuales y la equivocada para cualquier cosa que un cron o un worker con reintentos pueda enviar dos veces.

<Tip>
  Una buena clave se deriva de forma determinista del trabajo, no del intento. Si compones los identificadores que
  definen la unidad de trabajo, como `"{jobId}:{promptId}:{engine}"`, un reintento reproduce la misma clave de forma natural.
</Tip>

## Individual y lote difieren a propósito

Es el único punto en que los dos endpoints divergen, y suele confundir.

<CodeGroup>
  ```json Single — duplicate is an error theme={null}
  POST /v1/async/task
  → 409 Conflict

  {
    "success": false,
    "error": {
      "code": "RESOURCE_ALREADY_EXISTS",
      "message": "Task already exists.",
      "timestamp": "2026-07-09T04:12:00.000Z"
    }
  }
  ```

  ```json Batch — duplicate is a success theme={null}
  POST /v1/async/task/batch
  → 200 OK

  {
    "success": true,
    "summary": { "total": 1, "succeeded": 1, "failed": 0 },
    "results": [
      {
        "success": true,
        "index": 0,
        "task": { "id": "8f2c1e40-...", "status": "QUEUED", "...": "..." }
      }
    ]
  }
  ```
</CodeGroup>

El endpoint individual mantiene una semántica de creación explícita: pediste crear una tarea, la tarea ya existe,
eso es un conflicto y debes saberlo.

El endpoint de lote **absorbe los duplicados como éxito**. Su cliente habitual es un worker que reenvía un trabajo
completo tras reiniciarse, y marcar como fallido un lote de 500 elementos porque una clave ya estaba en cola anularía
el sentido de las claves de idempotencia. El `results[i].task` que recibes es la tarea *existente*, así que tu mapeo
de tareas sigue resolviéndose.

## Relacionar resultados con envíos

Hay dos identificadores independientes y te conviene usar ambos:

<ResponseField name="results[].index" type="integer">
  Corresponde 1:1 por posición con tu array de entrada. Úsalo para relacionar la respuesta del lote con las tareas que enviaste.
</ResponseField>

<ResponseField name="task.idempotencyKey" type="string">
  Se devuelve tal cual. Úsalo para relacionar el *webhook*, que llega minutos después, desordenado y sin referencia a tu array original.
</ResponseField>

<Note>
  Si no enviaste `idempotencyKey`, el campo se **omite** del objeto de tarea en lugar de devolverse como `null`.
  Internamente la tarea usa su propio uuid como clave, que es único por construcción y por tanto nunca deduplica.
</Note>

## Vida de la clave

Las claves de tareas con retención permanente siguen reservadas tras la finalización, el fallo y la ventana pública
de sondeo de 24 horas. Un 404 en el sondeo no libera la clave. Usa la misma clave para los reintentos de transporte
de un envío y una clave de ejecución nueva (por ejemplo, con el sufijo `:r1`) para una ejecución distinta. No se
exige formato UUID. Los registros que ya se habían movido al historial heredado antes de activarse la retención
permanente no reservan claves de forma retroactiva.
