# 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
se completa correctamente, los archivos generados. El mismo objeto se devuelve desde cada endpoint de abajo, para cada modelo.

## Buscar un modelo

El catálogo de los 1,496 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 conteos. |
| `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}` agrega el
JSON Schema completo de entrada y salida, los nombres canónicos de los parámetros, las reglas de precio y un ejemplo de entrada. Una
`category` o `mode` desconocida devuelve `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 cualquier cosa. Una entrada no válida devuelve `422 invalid_input` con el
  `param` problemático y no tiene costo.
- `?wait=N` (hasta 20 segundos) mantiene la llamada abierta hasta que la solicitud termina o se agota el tiempo.
- `?webhook_url=https://…` envía un POST firmado a esa URL cuando termina la solicitud (consulta [Webhooks](https://doitong.com/developers/docs/webhooks)).
- `?storage=permanent` o `?storage=temporary` elige 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 almacenan los archivos](#storage)).

La respuesta es `202 Accepted` mientras la solicitud sigue en proceso y `200 OK` cuando terminó durante la espera (o
cuando es una repetición, consulta 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; los fallidos y cancelados reciben un reembolso).

| Estado | Significado | Final |
|---|---|---|
| `queued` | Aceptada y cobrada; espera a un trabajador. `queue_position` puede mostrar su lugar en la fila. | no |
| `running` | El modelo está generando. | no |
| `succeeded` | Completada. `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 objeto de resultado propio 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` una vez que desaparecen los archivos).
- `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 se completa correctamente, `cost.settled` indica el costo del uso y el resto del máximo está
  en `cost.refunded` (consulta [Cobro según el uso](https://doitong.com/developers/docs/pricing#metered)).
- Cuando una solicitud falla, `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 long polling: la llamada devuelve la respuesta en cuanto la solicitud es final, o después de N segundos con
  el estado actual. Repetir esto es la forma más sencilla de esperar un resultado.
- `?logs=1` agrega 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 la más reciente a la más antigua. `limit` va de 1 a 100 (predeterminado 20); `status` y `model` filtran. La respuesta es
`{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "req_…" }`; pasa `cursor=<next_cursor>` para
la siguiente página.

## 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 iniciara el modelo: el estado cambia a `canceled`, los créditos de API se reembolsan y recibes `200`.
- Ya está generando: la cancelación se envía al modelo y recibes `202` con `"cancel_requested": true`. Si el modelo
  termina de todos modos, la solicitud se completa 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 su cobro de 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 todavía está `queued` o `running` aún 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 almacenan los archivos

Cada archivo de salida se copia a 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, luego se eliminan | Gratis |

- La configuración de la cuenta se aplica a las nuevas solicitudes: el interruptor "Conservar archivos para siempre" en el
  [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 `run` de MCP) la anula para esa ejecución.
- Cambiar la configuración nunca afecta 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: haz una recarga en un plazo de
  30 días o los archivos conservados se vuelven temporales y se eliminan 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árgalo pronto.
