# Solicitudes

Una solicitud es una llamada a un modelo. Tiene un id (`req_…`), un estado, su costo en créditos de API (1000 = 1 USD) y, cuando tiene éxito, los archivos generados. El mismo objeto se devuelve desde cada endpoint siguiente, para cada modelo.

## Buscar un modelo

El catálogo de los 1496 modelos es JSON público (no se necesita una clave):

```bash
curl "https://api.doitong.com/v1/models?category=text-to-video&lab=Google&limit=20"
```

| Parámetro de consulta | Significado |
|---|---|
| `q` | Palabras que deben aparecer todas en el nombre, id, fabricante o etiquetas; por ejemplo, `kling 3`. |
| `category` | Un id de categoría, por ejemplo, `text-to-video` o `upscale`. `GET https://api.doitong.com/v1/categories` muestra las 21 categorías con sus recuentos. |
| `lab` | El fabricante del modelo, por nombre o id; por ejemplo, `Google` o `black-forest-labs`. |
| `family` | Un id de `family` de una fila anterior: cada variante de un modelo (pro, fast, image-to-video, …). |
| `mode` | `exact` (el precio se calcula a partir de la entrada), `measured` (a partir de la duración o el tamaño de los archivos de entrada), `estimate` (un precio estimado por ejecución) o `metered` (se cobra según el uso real, hasta un máximo por ejecución). |
| `limit`, `cursor` | Tamaño de página (de 1 a 100, predeterminado 50) y el `next_cursor` de la página anterior. |

Cada fila tiene `id`, `name`, `lab`, `category`, `family`, `summary`, `tags`, `output_kind`, `docs_url` y un objeto `price` (consulta [Precios](https://doitong.com/developers/docs/pricing#price-objects)). `GET https://api.doitong.com/v1/models/{model_id}` añade el esquema JSON completo de entrada y salida, los nombres canónicos de los parámetros, las reglas de precios y un ejemplo de entrada. Una `category` o un `mode` desconocidos devuelven `400 invalid_parameter`.

## Ejecutar un modelo

```bash
curl -X POST "https://api.doitong.com/v1/run/{model_id}?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1842-cover" \
  -d '{"prompt": "..."}'
```

- El cuerpo es la entrada del modelo, un objeto JSON. Cada página de modelo enumera sus parámetros.
- La entrada se valida antes de cobrar nada. Una entrada no válida devuelve `422 invalid_input` con el `param` problemático y no cuesta nada.
- `?wait=N` (hasta 20 segundos) mantiene la llamada abierta hasta que finaliza la solicitud o se agota el tiempo.
- `?webhook_url=https://…` envía un POST firmado a esa URL cuando finaliza la solicitud (consulta [Webhooks](https://doitong.com/developers/docs/webhooks)).
- `?storage=permanent` o `?storage=temporary` determina cuánto tiempo se conservan los archivos de salida; sin este parámetro se aplica la configuración de la cuenta (consulta [Dónde se guardan los archivos](#storage)).

La respuesta es `202 Accepted` mientras la solicitud sigue procesándose y `200 OK` cuando finalizó durante la espera (o cuando es una reproducción, consulta más abajo). Los encabezados `Location` y `Doitong-Request-Id` contienen la URL y el id de la solicitud.

## Estados

Ciclo de vida de la solicitud: `queued` → `running` → `succeeded` | `failed` | `canceled` (finalizado; las solicitudes fallidas y canceladas se reembolsan).

| Estado | Significado | Final |
|---|---|---|
| `queued` | Aceptada y cobrada; espera a un trabajador. `queue_position` puede mostrar su posición en la cola. | no |
| `running` | El modelo está generando. | no |
| `succeeded` | Lista. `outputs` enumera los archivos. | sí |
| `failed` | El modelo o un límite la detuvo. `error` indica el motivo. Los créditos de API se reembolsan. | sí |
| `canceled` | Cancelada antes de producir un resultado. Los créditos de API se reembolsan. | sí |

Una solicitud que supera su límite de tiempo falla con el código `timeout` y se reembolsa: 15 minutos para imágenes, 60 minutos para video, 30 minutos para audio y todo lo demás.

## El objeto de solicitud

```json
{
  "id": "req_8k2m4q7x1z9c3v5b6n0p2r4t",
  "object": "request",
  "model": "flux/schnell",
  "status": "succeeded",
  "created_at": "2026-10-20T12:00:00Z",
  "started_at": "2026-10-20T12:00:01Z",
  "completed_at": "2026-10-20T12:00:02Z",
  "queue_position": null,
  "cost": { "credits": 3, "usd": "0.003", "charged": 3, "refunded": 0 },
  "input": { "prompt": "..." },
  "output": { "images": [ { "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "width": 1024, "height": 768 } ] },
  "outputs": [
    { "type": "image", "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "content_type": "image/jpeg",
      "width": 1024, "height": 768, "file_size": 183920, "stored": true }
  ],
  "error": null,
  "metrics": { "inference_seconds": 1.2 },
  "storage": { "mode": "permanent", "bytes": 183920, "expires_at": null, "expired": false },
  "urls": {
    "self": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t",
    "cancel": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel"
  }
}
```

- `outputs` es igual para todos los modelos: una entrada por cada archivo generado con `type` (`image`, `video`, `audio`, `file` o `3d`), una `url` en nuestro almacenamiento, `content_type` y, cuando se conocen, `file_size`, `width`, `height`, `duration`.
- `output` es el propio objeto de resultado del modelo (sus campos se enumeran en cada página de modelo) con las mismas URL.
- `storage` indica cómo se conservan los archivos: `mode` (`permanent` o `temporary`), `bytes` almacenados, `expires_at` (cuándo se elimina un archivo temporal, `null` para los permanentes) y `expired` (`true` cuando los archivos ya no están disponibles).
- `cost.credits` es el precio de la llamada en créditos de API (`cost.usd` en USD), `cost.charged` es lo debitado del saldo de API y `cost.refunded` lo que se devolvió. Para un modelo `metered`, `cost.credits` es el máximo retenido al inicio (`cost.held`); cuando la ejecución tiene éxito, `cost.settled` indica a cuánto ascendió el uso y el resto del máximo aparece en `cost.refunded` (consulta [Cobro según el uso](https://doitong.com/developers/docs/pricing#metered)).
- Cuando falla una solicitud, `error` es `{ "type": …, "code": …, "message": … }`.

## Leer una solicitud

```bash
curl "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

- `?wait=N` (hasta 20 segundos) hace una espera prolongada: la llamada devuelve la respuesta en cuanto la solicitud es final, o después de N segundos con el estado actual. Repetir esta llamada es la forma más sencilla de esperar un resultado.
- `?logs=1` añade las líneas del registro de progreso del modelo (las últimas 50) como `logs`.

Solo puedes leer solicitudes de tu propia cuenta; cualquier otro id devuelve `404 request_not_found`.

## Enumerar solicitudes

```bash
curl "https://api.doitong.com/v1/requests?limit=20&status=succeeded&model=flux/schnell" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

De más reciente a más antigua. `limit` va de 1 a 100 (predeterminado 20); `status` y `model` filtran los resultados. La respuesta es `{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "req_…" }`; pasa `cursor=<next_cursor>` para obtener la página siguiente.

## Cancelar una solicitud

```bash
curl -X POST "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

- Cancelada antes de que comenzara el modelo: el estado pasa a ser `canceled`, los créditos de API se reembolsan y recibes `200`.
- Ya está generando: la cancelación se transmite y recibes `202` con `"cancel_requested": true`. Si el modelo termina de todos modos, la solicitud tiene éxito normalmente y se cobra.
- Ya es final: `409 not_cancelable` (una solicitud que ya está `canceled` simplemente devuelve `200`).

## Eliminar los archivos de una solicitud

```bash
curl -X DELETE "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/files" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

Elimina ahora nuestras copias de los archivos de salida de la solicitud y detiene el cobro del almacenamiento. La respuesta es el objeto de solicitud con `storage.expired: true`; sus URL permanecen en el registro, pero dejan de funcionar. Volver a llamarlo no cambia nada (`200`). Una solicitud que aún está `queued` o `running` todavía no tiene archivos: `409 request_active`. Requiere `models:run`.

## Idempotencia: reintentar sin pagar dos veces

Las redes fallan. Para reintentar una ejecución de forma segura, envía un encabezado `Idempotency-Key` (cualquier cadena de hasta 255 caracteres, única por operación; por ejemplo, tu propio id de pedido):

- Misma clave y mismo cuerpo que antes: recibes la solicitud original (`200`, encabezado `Idempotent-Replayed: true`) y no se vuelve a cobrar nada.
- Misma clave y cuerpo después de una ejecución que falló antes de llegar al modelo (`upstream_error` o `internal_error` al enviar, `insufficient_credits`, `budget_exceeded`, `too_many_inflight`): no se produjo nada ni se cobró nada, por lo que el reintento ejecuta el modelo de nuevo en lugar de devolver el error.
- Misma clave con un cuerpo diferente: `409 idempotency_mismatch`.
- Las claves están vinculadas a la clave de API que las envió.

## Dónde se guardan los archivos

Cada archivo de salida se copia en nuestro almacenamiento antes de marcar la solicitud como `succeeded`, por lo que las URL de `outputs` están en nuestros propios hosts y puedes enlazarlas directamente. Tú eliges cuánto tiempo permanecen:

| Modo | Duración | Precio |
|---|---|---|
| `permanent` (predeterminado) | Hasta que los elimines | 100 créditos de API (0,10 $) por GB al mes, cobrados diariamente del saldo de API |
| `temporary` | 7 días y después se eliminan | Gratis |

- La configuración de la cuenta se aplica a las nuevas solicitudes: el interruptor "Conservar archivos para siempre" del [panel](https://doitong.com/developers/dashboard#balance), o `PATCH https://api.doitong.com/v1/account/storage` con `{"mode": "temporary"}`. `GET https://api.doitong.com/v1/account` lo muestra en `storage`, junto con los bytes que conservas y la estimación mensual.
- `?storage=permanent|temporary` en una ejecución (el argumento `storage` de la herramienta MCP `run`) lo sobrescribe para esa ejecución.
- Cambiar la configuración nunca afecta a los archivos que ya tienes; elimínalos con [`DELETE /requests/{id}/files`](#delete-files).
- Cuando el saldo de API no puede pagar el almacenamiento, no se cobra nada y recibes un correo electrónico: haz una recarga en un plazo de 30 días o los archivos conservados pasarán a ser temporales y se eliminarán 7 días después.

En el caso poco frecuente de que falle nuestra copia, la entrada tiene `"stored": false` y la URL es la original, de corta duración: descárgala pronto.
