# Jobs

> Consulte status e resultados de jobs, leia dicas de falha e créditos, verifique 100 jobs em uma chamada, cancele jobs e pare ou continue o Gerar vídeo Pro.

Source: https://nodaro.ai/pt-BR/docs/developers/api/jobs

Um **job** é uma unidade de geração no Nodaro: uma imagem, uma renderização de vídeo, um clipe de fala. A execução de um nó retorna o ID de um job, e uma execução de workflow cria um job para cada nó de IA. Os endpoints de jobs informam o status, o progresso, o resultado e os créditos de cada job. Você consulta um job periodicamente até ele chegar a `completed`, `failed` ou `cancelled` e, depois, lê o resultado em `output_data`.

## Endpoints
| Método | Caminho | O que faz |
| --- | --- | --- |
| `GET` | `/v1/jobs/:id/status` | O status enxuto para a consulta periódica: status, progresso, resultado e erro. |
| `GET` | `/v1/jobs/:id` | O job completo, incluindo o que foi enviado (`input_data`) e os créditos. |
| `GET` | `/v1/jobs` | Os seus jobs, dos mais recentes para os mais antigos, em páginas. |
| `GET` | `/v1/jobs/status?ids=…` | O status de até 100 jobs, com os IDs na query string. |
| `POST` | `/v1/jobs/batch-status` | O status de até 100 jobs, com os IDs no corpo. |
| `POST` | `/v1/jobs/:id/cancel` | Cancela um job e libera os créditos reservados para ele. |
| `DELETE` | `/v1/jobs/:id` | Exclui um job e a mídia privada que ele produziu. |
| `GET` | `/v1/component/execute/:jobId/wait-limit` | Quanto tempo o servidor espera por uma execução de componente. |
| `POST` | `/v1/generate-video-pro/:jobId/stop` | Interrompe uma execução do **Gerar vídeo Pro** (Generate Video Pro) e mantém os segmentos concluídos. |
| `POST` | `/v1/generate-video-pro/continue` | Continua uma execução do Gerar vídeo Pro como um novo job. |
| `POST` | `/v1/credits/video-pro-estimate` | Calcula o preço de uma execução do Gerar vídeo Pro sem iniciá-la. |

Um token OAuth precisa do escopo `jobs:read` para ler jobs. Tokens de API pessoais não precisam de escopo.

## Ler um job
**curl**

```bash
curl -s https://app.nodaro.ai/v1/jobs/0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10/status \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

```json
{
"data": {
"id": "0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10",
"status": "completed",
"progress": 100,
"output_data": { "imageUrl": "https://…/0f1a9c2e.png" },
"error_message": null,
"error_hint": null,
"credit_status": "committed"
}
}
```

**TypeScript SDK**

```ts
const { data } = await client.jobs.getStatus(jobId)
if (data.status === 'completed') console.log(data.output_data)

// The full record, with input_data and credits:
const { data: job } = await client.jobs.get(jobId)
```

**CLI**

```bash
nodaro jobs get 0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10 --json
```

`GET /v1/jobs/:id/status` foi feito para loops de consulta periódica: ele omite `input_data` e os campos de custo, por isso é mais leve que `GET /v1/jobs/:id`. Os dois respondem `{ "data": … }`, e os dois respondem `404` para um job que não existe ou não é seu. Os campos dos jobs usam snake_case, do jeito que o servidor os envia.

| Campo | Significado |
| --- | --- |
| `id` | O ID do job. |
| `status` | Em que ponto o job está. Veja [Status dos jobs](#job-statuses). |
| `progress` | De 0 a 100. |
| `output_data` | O resultado. Os jobs de mídia trazem `imageUrl`, `videoUrl` ou `audioUrl`, muitas vezes com `thumbnailUrl`. Os outros jobs trazem campos próprios, como `text` ou `json`. |
| `error_message` | Por que o job falhou, em palavras. |
| `error_hint` | Um motivo estruturado para dois tipos de falha. Veja [Por que um job falhou](#why-a-job-failed). |
| `credit_status` | `reserved`, `committed`, `refunded` ou `null`. Veja [Créditos de um job](#credits-of-a-job). |
| `recovering` | `true` enquanto a plataforma repara um job cujo worker parou depois que o modelo já tinha entregado o resultado. |
| `input_data` | Só no job completo. O que foi enviado, depois das correções: por exemplo, o `prompt` final, o seu próprio `userPrompt` e os IDs de `direction`. |
| `credits` | Só no job completo. Os créditos do job. |
| `job_type`, `source`, `source_detail` | Só no job completo. O tipo de job e de onde ele veio: `source` é `internal`, `mcp`, `app`, `cli`, `sdk`, `extension`, `web` ou `api`, e `source_detail` indica o cliente, como `sdk/1.10.0`. |
| `created_at`, `started_at`, `completed_at` | Só no job completo. Carimbos de data e hora. |

`input_data` e `output_data` são visões públicas: os campos que existem só para o servidor são removidos para todos os chamadores.

## Status dos jobs
| Status | Final | Significado |
| --- | --- | --- |
| `pending`, `queued`, `processing` | Não | O job está aguardando a execução ou em execução. |
| `pending_review` | Não | O resultado existe, e a implantação o retém para uma revisão humana. Continue consultando periodicamente. |
| `completed` | Sim | O resultado está em `output_data`. |
| `failed` | Sim | `error_message` e `error_hint` dizem por quê. |
| `cancelled` | Sim | Você ou a plataforma cancelou o job. |

**Jobs em recuperação.** Quando um worker para depois que o modelo já entregou o resultado, o job fica em `processing` com `recovering: true` enquanto a plataforma o repara. Depois, ele é concluído ou reembolsado por conta própria. Em modelos lentos, isso pode levar dezenas de minutos, mais do que a espera padrão do SDK. Um `JobTimeoutError` de `runAndWait` não cancela o job, então busque o job de novo mais tarde.

**Jobs retidos para revisão.** Em uma implantação que registra uma política de revisão, um job pode entrar em `pending_review`. O trabalho está feito, os créditos continuam `reserved` durante toda a retenção, e uma pessoa decide se o resultado é liberado. O job então termina como `completed` (aprovado), `failed` com uma dica `policy-block` (rejeitado) ou `cancelled`. Um job retido pode ser cancelado como qualquer job em andamento. Não envie a requisição de novo, porque uma duplicata também ficaria retida. Quando a implantação define um prazo de revisão, um job que ninguém revisa a tempo é rejeitado: ele termina como `failed`, a reserva é reembolsada e o resultado retido é excluído. Um job retido nunca é aprovado automaticamente. O `runAndWait` do SDK lança `JobHeldError` na primeira consulta periódica que encontra `pending_review`, e o `--watch` da CLI sai com o código `3`.

## Boas práticas de consulta periódica
- **Consulte periodicamente a cada 2 a 5 segundos.** Um job muda de estado em segundos, não em milissegundos.
- **As leituras de status não contam para o limite de taxa do token.** Só as execuções e as listagens de workflows contam para o limite por minuto de um token de API pessoal. Veja [Limites de taxa](https://nodaro.ai/docs/developers/api/rate-limits).
- **Acompanhe muitos jobs com uma chamada.** Use os [endpoints em lote](#poll-many-jobs-at-once) em vez de uma requisição por job.
- **Deixe o cliente esperar por você.** O `client.nodes.runAndWait` do SDK consulta periodicamente a cada 2 segundos por até 15 minutos, e o `--watch` da CLI consulta periodicamente até o job terminar.

## Por que um job falhou
A tabela de erros em [Erros](https://nodaro.ai/docs/developers/api/errors) cobre as requisições que nunca criaram um job. Um job que falha depois traz `error_message` e, em dois tipos de falha, também um `error_hint` estruturado.

**O filtro de segurança de um modelo bloqueou a requisição:**

```json
{ "kind": "safety-block", "class": "safety", "retried": true, "suggestedProvider": "nano-banana-pro" }
```

- `class` é `copyright`, `likeness` ou `safety`. Um bloqueio `copyright` ou `likeness` é definitivo: a mesma requisição nunca passa.
- Em alguns modelos, um filtro `safety` nem sempre é consistente. No [GPT Image 2](https://nodaro.ai/docs/models/image/gpt-image-2), no [GPT Image 2.5 Flare](https://nodaro.ai/docs/models/image/gpt-image-2-5-flare) e no [GPT Image 2.5 Sunburst](https://nodaro.ai/docs/models/image/gpt-image-2-5-sunburst), o Nodaro tenta a mesma requisição de novo uma vez, sem custo extra. `retried` informa se essa nova tentativa já aconteceu.
- `suggestedProvider` só aparece quando o modelo tem uma alternativa recomendada. É um ID de modelo real: envie o mesmo prompt e as mesmas referências para ele.

**A política de uma implantação rejeitou o job:**

```json
{ "kind": "policy-block", "policyId": "brand-safety", "reason": "This image was not approved for release.", "hookPoint": "result" }
```

- `reason` é um texto escrito para o seu usuário pela política da implantação. Mostre esse texto como está.
- `hookPoint` é `request` quando o job foi recusado antes de ser executado, e `result` quando o resultado dele foi rejeitado depois, inclusive por um revisor.
- A plataforma não tenta de novo depois de uma rejeição por política e não oferece outro modelo.

**Nenhuma outra falha tem `error_hint`.** Quando um modelo recusa a própria requisição, porque as configurações ou a mídia de entrada são inválidas para esse modelo, `error_message` informa isso: mude as configurações ou a mídia antes de executar de novo. Um erro do lado do modelo continua valendo uma nova tentativa, seja qual for o texto. `error_hint` aparece em todo payload de job que traz `error_message`: o job completo, as rotas de status, a lista e as duas rotas em lote.

## Créditos de um job
`credit_status` acompanha a reserva de créditos do job:

| Valor | Significado |
| --- | --- |
| `reserved` | Os créditos ficam reservados enquanto o job é executado. |
| `committed` | O job entregou o resultado e foi cobrado. |
| `refunded` | A reserva foi liberada, por exemplo, depois de um bloqueio de segurança. |
| `null` | O job não tem um registro de créditos para informar. |

Ele aparece em `GET /v1/jobs/:id`, `GET /v1/jobs/:id/status` e `GET /v1/jobs/status`, nunca em `GET /v1/jobs` nem em `POST /v1/jobs/batch-status`. Uma geração que termina em um bloqueio de segurança ou de política é sempre reembolsada. A rara exceção é um job cujos créditos já tinham sido liquidados antes de uma política rejeitar o resultado dele. Veja [Créditos](https://nodaro.ai/docs/developers/api/credits).

## Listar os seus jobs
`GET /v1/jobs` retorna os seus jobs, dos mais recentes para os mais antigos, como `{ data: Job[], next }`:

| Parâmetro de consulta | Significado |
| --- | --- |
| `limit` | Tamanho da página, até 100. |
| `cursor` | O valor `next` da página anterior. |
| `type` | A rota que criou o job, com o valor exato, como `llm-structured` ou `video-analysis`. |
| `origin` | O app cliente que enviou o job, com o valor exato, como `studio`. |
| `attachToCharacterId` | Os jobs de um personagem. Veja [Personagens](https://nodaro.ai/docs/developers/api/characters). |

`type` e `origin` podem ser combinados. Uma página pode ter menos linhas que `limit`, até nenhuma, e ainda trazer um `next`: continue paginando enquanto `next` estiver presente, nunca contando linhas.

```ts
const { data: runs, next } = await client.jobs.list({ type: 'llm-structured', origin: 'my-app' })
```

## Consultar periodicamente muitos jobs de uma vez
Dois endpoints retornam o status de até 100 jobs em uma única ida e volta:

**GET /v1/jobs/status**

```bash
curl -s "https://app.nodaro.ai/v1/jobs/status?ids=$JOB_A,$JOB_B,$JOB_C" \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

```json
{
"jobs": [
{ "id": "0f1a9c2e-…", "status": "completed", "output_data": { "imageUrl": "https://…/a.png" }, "error_message": null, "error_hint": null, "credit_status": "committed" },
{ "id": "7d3e5f60-…", "status": "processing", "output_data": null, "error_message": null, "error_hint": null, "credit_status": "reserved" }
]
}
```

**POST /v1/jobs/batch-status**

```bash
curl -s -X POST https://app.nodaro.ai/v1/jobs/batch-status \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"jobIds\": [\"$JOB_A\", \"$JOB_B\", \"$JOB_C\"]}"
```

A resposta é `{ "data": [ … ] }`, com `id`, `status`, `output_data`, `error_message` e `error_hint` para cada job, e sem `credit_status`.

Os IDs que não existem, ou que pertencem a outra pessoa, ficam de fora sem erro. Compare a resposta com a sua própria lista de IDs.

## Cancelar ou excluir um job
`POST /v1/jobs/:id/cancel` cancela um job e libera os créditos que ele reservou. A resposta é `{ "success": true, "cancelled": 1 }`. Um job retido em `pending_review` também pode ser cancelado.

`DELETE /v1/jobs/:id` exclui um job e a mídia privada que ele produziu, e responde `{ "success": true }`. Só o dono do job pode excluí-lo. Um job em execução é excluído do jeito que está, então cancele-o antes quando o trabalho dele precisar parar.

**curl**

```bash
curl -s -X POST https://app.nodaro.ai/v1/jobs/$JOB_ID/cancel \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

**TypeScript SDK**

```ts
const { cancelled } = await client.jobs.cancel(jobId)
await client.jobs.delete(jobId)
```

**CLI**

```bash
nodaro jobs cancel 0f1a9c2e-5b7d-4e8a-9c31-6d2f8b4a7e10
```

Para interromper uma execução de workflow inteira, cancele essa execução: veja [Execuções](https://nodaro.ai/docs/developers/api/executions).

## Execuções de componentes
`POST /v1/component/execute` executa um [**Componente** (Component)](https://nodaro.ai/docs/nodes/automate/component) salvo em segundo plano e responde `202` com `{ jobId }`. Consulte esse job periodicamente, como qualquer outro. O servidor dá 90 minutos a uma execução de componente, mais o orçamento de tempo de qualquer renderização longa dentro dela, como a renderização final de um nó [**Aplicar EDL** (Apply EDL)](https://nodaro.ai/docs/nodes/video/apply-edl).

Um cliente com um limite de tempo próprio pode perguntar quanto tempo o servidor vai esperar:

```bash
curl -s https://app.nodaro.ai/v1/component/execute/$JOB_ID/wait-limit \
  -H "Authorization: Bearer $NODARO_API_KEY"
```

```json
{ "data": { "budgetExcessMs": 0, "waitLimitMs": 5400000, "pendingBudgetedNodes": true } }
```

- `waitLimitMs` é a espera do servidor para esta execução: 90 minutos mais `budgetExcessMs`.
- `budgetExcessMs` fica em `0` até a execução iniciar uma renderização longa.
- `pendingBudgetedNodes` é `true` enquanto uma renderização longa ainda não começou e enquanto a própria execução ainda não começou. Por isso, um excesso `0` ainda não significa que não há nada longo dentro dela.

Os dois valores podem mudar durante a execução, então pergunte de novo quando `waitLimitMs` for atingido. A rota só responde ao dono da execução, com `404` para qualquer outra pessoa e para jobs que não são execuções de componentes. O próprio editor usa esta regra: espera 30 minutos por uma execução sem nada longo dentro, `waitLimitMs` depois que uma renderização longa começa e pelo menos 90 minutos enquanto `pendingBudgetedNodes` é `true`. Depois, pergunta de novo.

## Execuções do Gerar vídeo Pro
O [Gerar vídeo Pro](https://nodaro.ai/docs/nodes/video/generate-video-pro) faz vídeos longos um segmento por vez e salva o progresso entre os segmentos, então uma execução pode ser interrompida e continuada depois. Ele roda no Nodaro Cloud, e uma instalação self-hosted o executa pela conexão com o Nodaro Cloud.

### Interromper uma execução
`POST /v1/generate-video-pro/:jobId/stop` interrompe de forma controlada uma execução em `processing`:

- O segmento que está sendo gerado é abandonado e, mesmo assim, é cobrado, porque o modelo continua a renderizá-lo.
- Os segmentos restantes são pulados.
- Todos os segmentos concluídos são unidos no vídeo final do job.
- A parte não usada da reserva é reembolsada.

A resposta é `{ "jobId": "…", "stopping": true }`. Continue consultando o job periodicamente: ele termina como `completed`, com `output_data.pro.stopped` igual a `true` e com `stoppedAtSegment`. Já um job que ainda está em `pending` é cancelado com reembolso total.

### Continuar uma execução
`POST /v1/generate-video-pro/continue` inicia um **novo job** a partir de um job terminado. Ele reaproveita o plano do job original e todos os segmentos entregues antes de `fromSegment`, e gera de novo a partir dali:

```json
{ "fromJobId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "fromSegment": 4 }
```

- `fromSegment` começa em 1. Por padrão, é o primeiro segmento que não foi entregue.
- O job original precisa estar terminado: interrompido, com falha e pelo menos um segmento entregue, ou concluído. Um `fromSegment` explícito em uma execução concluída gera o final dela de novo.
- Você paga só pelos segmentos gerados de novo, mais a taxa fixa do Gerar vídeo Pro.
- A rota respeita o cabeçalho `Idempotency-Key`, então uma requisição repetida não inicia um segundo job.

A resposta é `{ jobId, continuedFromJobId, fromSegment, segmentCount }`. Consulte periodicamente o novo `jobId`.

**curl**

```bash
curl -s -X POST https://app.nodaro.ai/v1/generate-video-pro/$JOB_ID/stop \
  -H "Authorization: Bearer $NODARO_API_KEY"

curl -s -X POST https://app.nodaro.ai/v1/generate-video-pro/continue \
  -H "Authorization: Bearer $NODARO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4c1f0a7e-2b3d-4e5f-8a9b-0c1d2e3f4a5b" \
  -d "{\"fromJobId\": \"$JOB_ID\", \"fromSegment\": 4}"
```

**TypeScript SDK**

```ts
await client.videoPro.stop(jobId)
const { data } = await client.jobs.getStatus(jobId) // completes with a partial video

const { jobId: childId } = await client.videoPro.continueRun(jobId, { fromSegment: 4 })
```

**CLI**

```bash
nodaro video-pro stop 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d
nodaro video-pro continue 9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d --from-segment 4 --watch
```

As duas rotas respondem `404` para um job que não é seu e `400` para um job que não é uma execução do Gerar vídeo Pro.

### Estimar uma execução
`POST /v1/credits/video-pro-estimate` calcula o preço de uma execução do Gerar vídeo Pro sem criar um job nem reservar créditos:

```json
{ "provider": "gemini-omni-flash", "resolution": "720p", "duration": 12, "renderMethod": "keyframes", "segmentMode": "short" }
```

Em uma configuração de exemplo, a resposta é `{ "data": { "credits": 760, "upperBound": true } }`. Leia a resposta em tempo real para ver os preços atuais.

<TypeTable
type={{
provider: { type: 'string', description: 'O modelo de vídeo.', required: true },
resolution: { type: 'string', description: 'A resolução.', default: '720p' },
duration: { type: 'integer', description: 'A duração total em segundos, de 1 a 3.600.', default: '8' },
aspectRatio: { type: 'string', description: 'O formato do quadro.' },
renderMethod: { type: 'string', description: 'extend ou keyframes.' },
anchorMode: { type: 'string', description: 'upfront, progressive ou none.' },
contextTailSec: { type: 'number', description: 'De 2 a 15 segundos.' },
segmentMode: { type: 'string', description: 'short, long ou max. Não pode ser combinado com preferredSegmentSec nem com segmentDurations.' },
preferredSegmentSec: { type: 'integer', description: 'De 4 a 15 segundos.' },
segmentDurations: { type: 'integer[]', description: 'De 1 a 24 durações explícitas de segmento, cada uma de 1 a 30 segundos.' },
planOnly: { type: 'boolean', description: 'Calcula o preço só da etapa de planejamento.' },
}}
/>

- Com `segmentMode` `short` ou `long`, a execução primeiro atribui ações completas a trechos da origem, e `upperBound: true` significa que o valor é o limite da reserva antes do planejamento. A cobrança final segue o plano real.
- Uma estimativa `planOnly` cobre a taxa de planejamento e retorna `upperBound: false`. Os valores `sourceSegmentDurations` e `planCheckpoint` dela podem ser enviados de volta como `sourceSegmentDurations` e `seedPlan`, com o mesmo modo e as mesmas configurações.

Leia [Gerar vídeo Pro](https://nodaro.ai/docs/nodes/video/generate-video-pro) para saber como a segmentação funciona e o que cada configuração faz.

## Frequently asked questions

### O que é um job na API do Nodaro?

Um job é uma unidade de geração, como uma imagem, uma renderização de vídeo ou um clipe de fala. POST /v1/ com um tipo de nó retorna o ID de um job, e uma execução de workflow cria um job para cada nó de IA que executa.

### Com que frequência devo consultar periodicamente um job do Nodaro?

A cada 2 a 5 segundos. Um job muda de estado em segundos, não em milissegundos. Para acompanhar muitos jobs, use GET /v1/jobs/status ou POST /v1/jobs/batch-status, que retornam até 100 jobs em uma chamada.

### O que significa o status pending_review?

A implantação retém o resultado para uma revisão humana antes de liberá-lo. O job continua em andamento, e os créditos dele continuam reservados. Continue consultando periodicamente: ele termina como completed, failed ou cancelled.

### Como sei se um job com falha foi reembolsado?

Leia credit_status em GET /v1/jobs/:id ou GET /v1/jobs/:id/status. O valor é reserved, committed ou refunded. Uma geração bloqueada por um filtro de segurança ou por uma política da implantação é sempre reembolsada.

### Posso interromper uma execução do Gerar vídeo Pro e manter o que ela já fez?

Sim. POST /v1/generate-video-pro/:jobId/stop mantém os segmentos concluídos, une todos eles no vídeo final e reembolsa a parte não usada da reserva. Depois, você pode continuar a execução como um novo job.
