# Pedidos

Um pedido é uma chamada de um modelo. Tem um id (`req_…`), um estado, o seu custo em créditos de API (1000 = 1 USD) e, quando é concluído com sucesso, os ficheiros gerados. O mesmo objeto é devolvido por todos os endpoints abaixo, para todos os modelos.

## Encontrar um modelo

O catálogo dos 1496 modelos é um JSON público (não é necessária uma chave):

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

| Parâmetro de consulta | Significado |
|---|---|
| `q` | Palavras que têm de aparecer todas no nome, id, fabricante ou etiquetas, por exemplo `kling 3`. |
| `category` | Um id de categoria, por exemplo `text-to-video` ou `upscale`. `GET https://api.doitong.com/v1/categories` lista as 21 categorias com as respetivas contagens. |
| `lab` | O fabricante do modelo, por nome ou id, por exemplo `Google` ou `black-forest-labs`. |
| `family` | Um id de `family` de uma linha anterior: todas as variantes de um modelo (pro, fast, image-to-video, …). |
| `mode` | `exact` (o preço é calculado a partir da entrada), `measured` (a partir da duração ou do tamanho dos ficheiros de entrada), `estimate` (um preço estimado por execução) ou `metered` (cobrado com base na utilização real, até a um máximo por execução). |
| `limit`, `cursor` | Tamanho da página (1 a 100, predefinição 50) e o `next_cursor` da página anterior. |

Cada linha tem `id`, `name`, `lab`, `category`, `family`, `summary`, `tags`, `output_kind`, `docs_url` e um objeto `price` (consulte [Preços](https://doitong.com/developers/docs/pricing#price-objects)). `GET https://api.doitong.com/v1/models/{model_id}` acrescenta o JSON Schema completo da entrada e da saída, os nomes canónicos dos parâmetros, as regras de preço e um exemplo de entrada. Uma `category` ou `mode` desconhecida resulta em `400 invalid_parameter`.

## Executar um 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": "..."}'
```

- O corpo é a entrada do modelo, um objeto JSON. Cada página de modelo lista os seus parâmetros.
- A entrada é validada antes de qualquer cobrança. Uma entrada inválida devolve `422 invalid_input` com o `param` que causou o problema e não tem qualquer custo.
- `?wait=N` (até 20 segundos) mantém a chamada aberta até o pedido terminar ou o tempo esgotar-se.
- `?webhook_url=https://…` envia um POST assinado para esse URL quando o pedido termina (consulte [Webhooks](https://doitong.com/developers/docs/webhooks)).
- `?storage=permanent` ou `?storage=temporary` escolhe durante quanto tempo os ficheiros de saída são mantidos; sem este parâmetro, aplica-se a definição da conta (consulte [Onde ficam os ficheiros](#storage)).

A resposta é `202 Accepted` enquanto o pedido ainda está a ser processado e `200 OK` quando terminou dentro do período de espera (ou quando é uma repetição, conforme descrito abaixo). Os cabeçalhos `Location` e `Doitong-Request-Id` contêm o URL e o id do pedido.

## Estados

Ciclo de vida do pedido: `queued` → `running` → `succeeded` | `failed` | `canceled` (final; falhas e cancelamentos são reembolsados).

| Estado | Significado | Final |
|---|---|---|
| `queued` | Aceite e cobrado, à espera de um worker. `queue_position` pode mostrar a posição na fila. | não |
| `running` | O modelo está a gerar. | não |
| `succeeded` | Concluído. `outputs` lista os ficheiros. | sim |
| `failed` | O modelo ou um limite interrompeu-o. `error` indica o motivo. Os créditos de API são reembolsados. | sim |
| `canceled` | Cancelado antes de produzir um resultado. Os créditos de API são reembolsados. | sim |

Um pedido que exceda o seu limite de tempo falha com o código `timeout` e é reembolsado: 15 minutos para imagens, 60 minutos para vídeo, 30 minutos para áudio e tudo o resto.

## O objeto do pedido

```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` é igual para todos os modelos: uma entrada por ficheiro gerado com `type` (`image`, `video`, `audio`, `file` ou `3d`), um `url` no nosso armazenamento, `content_type` e, quando conhecidos, `file_size`, `width`, `height`, `duration`.
- `output` é o próprio objeto de resultado do modelo (os respetivos campos estão listados em cada página de modelo), com os mesmos URLs.
- `storage` indica como os ficheiros são mantidos: `mode` (`permanent` ou `temporary`), `bytes` armazenados, `expires_at` (quando um ficheiro temporário é eliminado, `null` para os permanentes) e `expired` (`true` quando os ficheiros já desapareceram).
- `cost.credits` é o preço da chamada em créditos de API (`cost.usd` em USD), `cost.charged` é o montante debitado do saldo de API e `cost.refunded` é o montante devolvido. Para um modelo `metered`, `cost.credits` é o máximo retido no início (`cost.held`); quando a execução é concluída com sucesso, `cost.settled` indica o custo efetivo da utilização e o restante do máximo está em `cost.refunded` (consulte [Cobrado com base na utilização](https://doitong.com/developers/docs/pricing#metered)).
- Quando um pedido falha, `error` é `{ "type": …, "code": …, "message": … }`.

## Consultar um pedido

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

- `?wait=N` (até 20 segundos) faz long polling: a chamada devolve o resultado assim que o pedido chega a um estado final ou, após N segundos, o estado atual. Repetir esta chamada é a forma mais simples de aguardar um resultado.
- `?logs=1` acrescenta as linhas do registo de progresso do modelo (as últimas 50) como `logs`.

Só pode consultar pedidos da sua própria conta; qualquer outro id resulta em `404 request_not_found`.

## Listar pedidos

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

Do mais recente para o mais antigo. `limit` varia entre 1 e 100 (predefinição 20); `status` e `model` funcionam como filtros. A resposta é `{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "req_…" }`; passe `cursor=<next_cursor>` para obter a página seguinte.

## Cancelar um pedido

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

- Cancelado antes de o modelo começar: o estado passa a `canceled`, os créditos de API são reembolsados e recebe `200`.
- Já em geração: o cancelamento é encaminhado e recebe `202` com `"cancel_requested": true`. Se o modelo terminar mesmo assim, o pedido é concluído normalmente e cobrado.
- Já num estado final: `409 not_cancelable` (um pedido que já esteja `canceled` devolve simplesmente `200`).

## Eliminar os ficheiros de um pedido

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

Elimina agora as nossas cópias dos ficheiros de saída do pedido e interrompe a cobrança do armazenamento. A resposta é o objeto do pedido com `storage.expired: true`; os URLs permanecem no registo, mas deixam de funcionar. Chamá-lo novamente não altera nada (`200`). Um pedido que ainda esteja `queued` ou `running` ainda não tem ficheiros: `409 request_active`. Requer `models:run`.

## Idempotência: repetir sem pagar duas vezes

As redes falham. Para repetir uma execução em segurança, envie um cabeçalho `Idempotency-Key` (qualquer sequência com até 255 caracteres, única por operação, por exemplo o seu próprio id de encomenda):

- A mesma chave e o mesmo corpo de antes: recebe o pedido original (`200`, cabeçalho `Idempotent-Replayed: true`) e não há nova cobrança.
- A mesma chave e o mesmo corpo depois de uma execução que falhou antes de chegar ao modelo (`upstream_error` ou `internal_error` no envio, `insufficient_credits`, `budget_exceeded`, `too_many_inflight`): nada foi produzido nem cobrado, por isso a repetição executa novamente o modelo em vez de devolver a falha.
- A mesma chave com um corpo diferente: `409 idempotency_mismatch`.
- As chaves estão associadas à chave de API que as enviou.

## Onde ficam os ficheiros

Cada ficheiro de saída é copiado para o nosso armazenamento antes de o pedido ser marcado como `succeeded`, pelo que os URLs em `outputs` apontam para os nossos próprios hosts e podem ser ligados diretamente. A duração da retenção é uma escolha sua:

| Modo | Duração | Preço |
|---|---|---|
| `permanent` (predefinição) | Até os eliminar | 100 créditos de API (0,10 $) por GB por mês, cobrados diariamente ao saldo de API |
| `temporary` | 7 dias, depois são eliminados | Gratuito |

- A definição da conta aplica-se aos novos pedidos: o interruptor "Guardar ficheiros para sempre" no [dashboard](https://doitong.com/developers/dashboard#balance) ou `PATCH https://api.doitong.com/v1/account/storage` com `{"mode": "temporary"}`. `GET https://api.doitong.com/v1/account` mostra-a em `storage`, juntamente com os bytes que mantém e a estimativa mensal.
- `?storage=permanent|temporary` numa execução (o argumento `storage` da ferramenta `run` do MCP) substitui-a nessa execução.
- Alterar a definição nunca afeta os ficheiros que já possui; elimine-os com [`DELETE /requests/{id}/files`](#delete-files).
- Quando o saldo de API não consegue pagar o armazenamento, nada é cobrado e recebe um email: faça um carregamento no prazo de 30 dias ou os ficheiros mantidos passam a temporários e são eliminados 7 dias depois.

No caso raro de a nossa cópia falhar, a entrada tem `"stored": false` e o URL é o original, de curta duração: descarregue-o rapidamente.
