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

# Envía tareas en bloque: lotes y cuadrículas de ubicación

> Elige entre el lote de 500 tareas, el objeto lote de 10.000 tareas y la cuadrícula de ubicación, y después envía y lee los resultados.

Tres endpoints crean muchas tareas en una sola solicitud. Cada uno crea tareas normales: el sondeo, los webhooks, los créditos y los límites del plan son los mismos que para una tarea individual.

## Compara los tres endpoints

| | `POST /v1/async/task/batch` | `POST /v1/batches` | `POST /v1/async/task/grid` |
| - | - | - | - |
| Qué envías | Un array JSON de solicitudes de tarea | `{ items, webhook?, metadata?, idempotencyKey? }` | Una solicitud de tarea más `grid` |
| Máximo de tareas | 500 | 10.000 | 81 (una cuadrícula de 9 × 9) |
| Cuerpo máximo | 8 MiB | 32 MiB | 1 MiB |
| Estado de éxito | `200` | `202` al crearse, `200` para un `idempotencyKey` repetido | `200` |
| Validación | Cada elemento por separado; los elementos erróneos fallan en su sitio | Todos los elementos primero; un solo elemento erróneo rechaza el lote entero con `400` y no se guarda nada | Primero el punto central; si se rechaza, se rechaza toda la cuadrícula |
| Por encima del límite del plan | Los elementos sobrantes fallan con `PLAN_CONCURRENCY_LIMIT`; tú los reenvías | Los elementos esperan en el servidor y se liberan a medida que hay hueco | Los puntos sobrantes fallan en su sitio |
| Progreso | Sondea cada tarea | Recuentos de `GET /v1/batches/{id}` | `GET /v1/async/task/grid/{id}` |
| Señal de finalización | Un webhook por tarea | Un webhook por tarea y después `batch.completed` | Un webhook por tarea |

## Cuál usar

* **Hasta 500 tareas y tú gestionas los reintentos:** `POST /v1/async/task/batch`. Recibes todos los ids de tarea en la respuesta.
* **Más tareas que la concurrencia de tu plan, o un trabajo diario de miles:** `POST /v1/batches`. El servidor guarda los elementos y los va enviando según lo permitan el [límite de tu plan](/es/concepts/credits-and-limits.md#concurrencia-del-plan) y la cola de cada motor, así que no tienes que reintentar los `429` ni los `503` tú mismo.
* **Un mismo prompt preguntado desde muchos puntos alrededor de un lugar:** `POST /v1/async/task/grid`.

## `POST /v1/async/task/batch`

Envía un array de hasta 500 solicitudes de tarea ([referencia](/es/api-reference/create-task-batch.md)). Se pueden mezclar motores.

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/async/task/batch \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    { "taskType": "CHATGPT", "idempotencyKey": "job-42:p1:chatgpt", "payload": { "prompt": "best CRM for a small team", "country": "US" } },
    { "taskType": "GEMINI", "idempotencyKey": "job-42:p1:gemini", "payload": { "prompt": "best CRM for a small team", "country": "US" } }
  ]'
```

Una solicitud bien formada devuelve `200`. `results[i].index` coincide con la posición en tu array, y cada resultado tiene `task` o `error`:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "summary": { "total": 2, "succeeded": 1, "failed": 1 },
  "results": [
    { "success": true, "index": 0, "task": { "id": "8f2c1e40-...", "status": "QUEUED", "...": "..." }, "credits": { "creditsToCharge": 0, "creditsCharged": 0 } },
    {
      "success": false,
      "index": 1,
      "error": {
        "code": "PLAN_CONCURRENCY_LIMIT",
        "message": "free plan allows 2 concurrent tasks; 2 already active",
        "details": { "plan": "free", "limit": 2, "source": "plan", "active": 2, "retryAfterSec": 5 },
        "timestamp": "2026-10-19T04:12:00.000Z"
      }
    }
  ]
}
```

La solicitud entera se rechaza, sin crear ninguna tarea, en estos casos:

| Estado | Código | Cuándo |
| - | - | - |
| `400` | `VALIDATION_ERROR` | JSON no válido, no es un array, está vacío o tiene más de 500 elementos |
| `413` | `PAYLOAD_TOO_LARGE` | Cuerpo de más de 8 MiB |
| `503` | `SERVER_OVERLOADED` | La admisión está saturada. Reintenta tras `Retry-After`. |
| `503` | `SERVER_OVERLOADED` | La cola global está llena. Reintenta tras `Retry-After`. |

Un `idempotencyKey` repetido devuelve la tarea existente como éxito. Consulta [Idempotencia](/es/concepts/idempotency.md).

## `POST /v1/batches`

Envía hasta 10.000 elementos ([referencia](/es/api-reference/create-batch.md)). Cada elemento acepta los campos de tarea `taskType`, `payload`, `priority` e `idempotencyKey`, más un `customId` opcional (hasta 128 caracteres) que se devuelve en los resultados. Indica `webhook.url` en el lote, no en los elementos.

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/batches \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "idempotencyKey": "daily-2026-10-19",
    "webhook": { "url": "https://hooks.example.com/querying" },
    "metadata": { "run": "daily" },
    "items": [
      { "customId": "crm-chatgpt-us", "taskType": "CHATGPT", "payload": { "prompt": "best CRM for a small team", "country": "US" } },
      { "customId": "crm-gemini-us", "taskType": "GEMINI", "payload": { "prompt": "best CRM for a small team", "country": "US" } }
    ]
  }'
```

La respuesta es `202` con el lote. Los primeros elementos ya están enviados:

```json theme={"theme":"css-variables"}
{
  "success": true,
  "batch": {
    "id": "3f6b2a10-...",
    "status": "OPEN",
    "total": 2,
    "counts": { "pending": 0, "queued": 2, "processing": 0, "completed": 0, "failed": 0, "rejected": 0 },
    "createdAt": "2026-10-19T09:00:00.000Z",
    "completedAt": null,
    "idempotencyKey": "daily-2026-10-19",
    "webhook": { "url": "https://hooks.example.com/querying" },
    "metadata": { "run": "daily" }
  }
}
```

### Reglas del sobre

| Campo | Regla |
| - | - |
| `items` | De 1 a 10.000 objetos |
| `webhook.url` | Las mismas [reglas de URL](/es/concepts/webhooks.md#reglas-de-la-url) que una tarea. Se aplica a todos los elementos. |
| `metadata` | Un objeto de como máximo 8 KB en JSON, devuelto sin cambios |
| `idempotencyKey` | De 1 a 256 caracteres. Una repetición devuelve el primer lote con `200`. |
| `idempotencyKey` del elemento | Debe ser único dentro del lote |
| `webhook` del elemento | Se rechaza. Usa el `webhook.url` del lote. |

### Validación de todo o nada

Cada elemento pasa las mismas comprobaciones que una tarea individual antes de guardar nada: campos, alcance de la clave, motores privados, endpoints dedicados, región y `payload.location`. Si algún elemento falla, la respuesta es `400 VALIDATION_ERROR`, no se guarda nada y `details` enumera hasta 50 elementos con fallos:

```json theme={"theme":"css-variables"}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "1 of 2 items are invalid; the batch was not stored",
    "details": {
      "invalidItems": 1,
      "items": [
        { "index": 1, "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": [{ "field": "payload.prompt", "message": "payload.prompt or payload.query is required" }] }
      ]
    },
    "timestamp": "2026-10-19T09:00:00.000Z"
  }
}
```

### Cómo se ejecutan los elementos

El lote libera elementos como tareas cuando el límite de tu plan y la cola del motor tienen hueco. Las cuentas se turnan, así que un lote grande no retrasa uno pequeño de otra cuenta. Cada elemento liberado pasa por la misma admisión que una tarea individual: crea una retención de créditos y cuenta para el límite diario de créditos de la clave.

Cuando se rechaza un elemento, el código decide qué ocurre:

| Código | Elemento |
| - | - |
| `PLAN_CONCURRENCY_LIMIT`, `KEY_DAILY_CREDIT_LIMIT`, `SERVER_OVERLOADED`, `DB_UNAVAILABLE`, `ENQUEUE_ERROR` | Sigue `PENDING` y se vuelve a intentar más tarde |
| `QUEUE_BACKPRESSURE`, `ENGINE_UNAVAILABLE` | Sigue `PENDING` y se vuelve a intentar más tarde |
| `API_KEY_INACTIVE` | `REJECTED`: la clave del lote se revocó o caducó, o su subcuenta se suspendió |
| Cualquier otro código, incluido `INSUFFICIENT_CREDITS` | `REJECTED` |

El lote pasa a `COMPLETED` cuando no queda ningún elemento pendiente y todas las tareas han terminado. Con `webhook.url`, el servidor publica entonces el [evento `batch.completed`](/es/concepts/webhooks#evento-de-finalización-del-lote).

### Lee el progreso y los resultados

`GET /v1/batches/{id}` devuelve el lote con `counts` por estado de elemento: `pending`, `queued`, `processing`, `completed`, `failed`, `rejected`. Sondearlo también hace avanzar los elementos pendientes.

```bash theme={"theme":"css-variables"}
curl https://api.querying.ai/v1/batches/3f6b2a10-... \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

`GET /v1/batches/{id}/items` enumera los elementos en orden de envío:

```bash theme={"theme":"css-variables"}
curl "https://api.querying.ai/v1/batches/3f6b2a10-.../items?status=completed&include=response&limit=20" \
  -H "Authorization: Bearer $QUERYING_API_KEY"
```

| Parámetro | Descripción |
| - | - |
| `status` | Uno de `pending`, `queued`, `processing`, `completed`, `failed`, `rejected` |
| `include=response` | Añade el `response` o el `error` de cada tarea terminada |
| `limit` | 100 por defecto, 1.000 como máximo. Con `include=response`: 20 por defecto, 100 como máximo. |
| `cursor` | El `nextCursor` de la página anterior |

```json theme={"theme":"css-variables"}
{
  "success": true,
  "items": [
    {
      "index": 0,
      "customId": "crm-chatgpt-us",
      "taskType": "CHATGPT",
      "status": "COMPLETED",
      "taskId": "8f2c1e40-...",
      "completedAt": "2026-10-19T09:01:40.000Z",
      "response": { "text": "...", "sources": [] }
    }
  ],
  "nextCursor": null
}
```

Un elemento `REJECTED` lleva `error: { code, message }`. Los cuerpos de resultado solo se incluyen dentro de la ventana de sondeo de 24 horas de [Tareas asíncronas](/es/concepts/async-tasks.md#retención). Consulta también [Obtener un lote](/es/api-reference/get-batch.md) y [Listar los elementos de un lote](/es/api-reference/list-batch-items.md).

## `POST /v1/async/task/grid`

Haz un mismo prompt desde cada punto de una cuadrícula cuadrada alrededor de un centro ([referencia](/es/api-reference/create-task-grid.md)). Cada punto se convierte en una tarea con ese punto como `payload.location`, así que el motor debe aceptar una ubicación: `CHATGPT`, `GEMINI`, `GOOGLE`, `AIMODE` o `GOOGLE_SERP`. No envíes `payload.location` tú mismo.

```bash theme={"theme":"css-variables"}
curl -X POST https://api.querying.ai/v1/async/task/grid \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskType": "CHATGPT",
    "idempotencyKey": "soho-coffee-2026-10-19",
    "payload": { "prompt": "best coffee shops near me for working on a laptop", "country": "US" },
    "grid": { "center": { "latitude": 40.72288, "longitude": -73.99875 }, "size": 3, "radiusMeters": 1500 }
  }'
```

| Campo | Regla |
| - | - |
| `grid.size` | `3`, `5`, `7` o `9` puntos por lado (de 9 a 81 tareas) |
| `grid.radiusMeters` | De 100 a 50.000: la distancia del centro al punto medio de cada borde. Los puntos están separados `2 × radius ÷ (size − 1)`. |
| `grid.center` | Latitud y longitud en grados decimales. La cuadrícula debe quedar por debajo de 89 grados de latitud. |
| `idempotencyKey` | Opcional, de 1 a 200 caracteres. Se convierte en el id de la cuadrícula. |
| `priority`, `webhook` | Se aplican a todos los puntos |

La fila 0 es el borde norte y la columna 0 el borde oeste. La respuesta devuelve `grid` (con `id` y `spacingMeters`), un `summary` y `points` en orden de filas, cada uno con `row`, `col`, `location` y `task` o `error`.

El punto central se admite primero, solo. Si se rechaza (por ejemplo, `422 LOCATION_UNSUPPORTED`, `403 KEY_SCOPE_DENIED`, `402 INSUFFICIENT_CREDITS` o un plan lleno), ese error es la respuesta para toda la cuadrícula y no se crea ninguna tarea. Los puntos posteriores al centro que se rechazan por capacidad fallan en su sitio con `success: false`.

Vuelve a leer una cuadrícula con [`GET /v1/async/task/grid/{id}`](/es/api-reference/get-task-grid). Añade `?include=response` para obtener los resultados de las tareas terminadas. Los puntos que nunca se admitieron no aparecen, y el id de cuadrícula de otra cuenta devuelve `404`. Reenviar la misma cuadrícula con el mismo id admite solo los puntos que faltan; consulta [Idempotencia](/es/concepts/idempotency.md#cuadrículas-de-ubicación).

## Siguientes pasos

> **[Idempotencia](/es/concepts/idempotency.md)**
>
> Cómo trata cada endpoint una clave repetida.

> **[Créditos y límites](/es/concepts/credits-and-limits.md)**
>
> Concurrencia del plan y ritmo de envío.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.