# Tabela de erros

Os códigos de arquivos/CDN (v12) estão logo abaixo; os da v13 vêm depois, por família.

Formato: `{"error": "<codigo>", "message": "..."}`; alguns trazem campos extras (`signature`, `reasons`).
Regra geral: **4xx = corrija a requisição, não repita igual** (exceto `429`); **5xx e `429` = repita com espera**.

| Código (`error`) | HTTP | Quando acontece | Ação recomendada |
|---|---|---|---|
| `unauthorized` | 401 | token ausente, malformado, expirado, revogado, enviado na query string ou só cookie | **Não repita.** Confira o cabeçalho `Authorization: Bearer nbk_…`; se expirou ou foi revogado, peça um token novo a um admin. A resposta é genérica de propósito. |
| `forbidden` | 403 | o token não tem o escopo da rota (ex.: `assets:read` tentando enviar) ou token restrito tentando criar galeria | **Não repita.** Peça um token com o escopo certo (`assets:read`, `assets:write`, `galleries:manage`). |
| `not_found` | 404 | galeria ou arquivo inexistente, de outro time, ou fora das galerias permitidas ao token (indistinguíveis) | Confira `key` da galeria e o `name` (use `%2F` para `/`). Não repita igual. |
| `invalid_request` | 400 | nome de arquivo fora das regras, cursor malformado, formulário sem `file`, nome de galeria vazio ou longo | Corrija o dado (veja as regras de nome na [Limites e cotas](/operacao/limites-e-cotas/)). Não repita igual. |
| `bad_json` | 400 | corpo JSON malformado | Corrija o JSON e o `Content-Type: application/json`. |
| `unsupported_media_type` | 415 | `Content-Type` da requisição inesperado (ex.: JSON onde se espera `multipart/form-data`) | Use o `Content-Type` da rota (`multipart/form-data` no upload, `application/json` nos demais). |
| `payload_too_large` | 413 | arquivo acima de 25 MB ou imagem acima de 40 MP | Reduza/comprima o arquivo. Não repita igual. |
| `too_large` | 413 | corpo da requisição acima do limite aceito pelo servidor (antes de olhar o arquivo) | Envie um arquivo menor (máximo 25 MB). |
| `quota_exceeded` | 413 | cota do time estourada (5 GB ou 10 000 arquivos) | Exclua arquivos antigos (`DELETE`) ou peça aumento de cota. Repetir só funciona depois de liberar espaço. |
| `unsupported_type` | 415 | tipo não reconhecido ou imagem ilegível | Confira se o arquivo não está corrompido. Não repita igual. |
| `extension_mismatch` | 415 | a extensão declara uma classe e o conteúdo é outra (ex.: `.png` que é um executável) | Corrija o nome/extensão ou envie o arquivo certo. |
| `class_not_allowed` | 415 | a classe do arquivo não é permitida no bucket | Use outro tipo de arquivo ou outro bucket. Não repita igual. |
| `malware_detected` | 422 | antivírus, regras YARA ou reputação por hash detectaram ameaça (campo extra `signature`) | **Não reenvie.** O arquivo está infectado ou foi marcado; investigue a origem. A amostra não é guardada. |
| `svg_unsafe` | 422 | SVG com conteúdo ativo ou fora do padrão (campo extra `reasons[]`) | **Não reenvie o mesmo SVG.** Veja a [SVG recusado](/cdn/classes-de-arquivo-e-varredura/#por-que-meu-svg-foi-recusado-e-como-reexportar) e reexporte. |
| `archive_encrypted` | 422 | compactado cifrado ou que não temos como varrer | Envie sem senha. |
| `archive_bomb` | 422 | compactado acima dos limites (expansão, razão de compressão, número de entradas ou profundidade) | Divida ou reduza o pacote. Não reenvie igual. |
| `archive_unsupported` | 422 | formato compactado que não inspecionamos (7z, rar, xz, bzip2) | Reempacote como `.zip` ou `.tar.gz`. |
| `idempotency_conflict` | 409 (upload de galeria) / 422 (demais rotas) | mesma `Idempotency-Key` com outro corpo | Gere uma chave nova (ou envie o mesmo corpo de antes). |
| `rate_limited` | 429 | rate-limit por token ou por IP | **Repita** depois de `Retry-After` segundos (cabeçalho); sem o cabeçalho, espere 30 s e dobre a cada tentativa seguida, até 5 vezes. Reduza a concorrência. |
| `unavailable` | 503 | borda ou scanner indisponível | **Seguro repetir** (com `Idempotency-Key`): espere `Retry-After`; sem ele, backoff de 5, 10, 20, 40 s. Se persistir, pare e avise o time. |
| `authz_unavailable` | 503 | o motor de autorização falhou (a API nega por segurança em vez de arriscar) | Igual a `unavailable`: repita com backoff; é transitório. |

## Erros da v13 (projetos, serviços, deploys, bancos)

Mesmo formato `{"error", "message"}`. Legenda da ação: **corrigir** (altere a requisição), **repetir** (com espera), **parar** (peça a uma pessoa).

### Autenticação, autorização e anti-IDOR

| Código | HTTP | Significado | Ação |
|---|---|---|---|
| `unauthorized` | 401 | token ausente, inválido, expirado ou revogado. Resposta genérica de propósito | **parar**: não tente de novo com o mesmo token |
| `forbidden` | 403 | o token existe mas não tem o escopo da rota (`projects:write`, `deploy`, `env:write`, `env:reveal`, `domains:write`, `volumes:write`, `databases:write`, `logs:read`, `templates:deploy`) ou `env:reveal` sem step-up na criação do token | **parar**: peça um token com o escopo certo ([Tokens e escopos](/seguranca/tokens-e-escopos/)) |
| `not_found` | 404 | o recurso não existe **ou não é do seu time**, ou fica fora dos `project_ids` do token. Os casos são indistinguíveis de propósito (anti-IDOR) | **corrigir** o id/nome. **Nunca** tente descobrir qual dos casos é: variar ids para "testar" só gera ruído e 429 |

### Validação e conflito

| Código | HTTP | Significado | Ação |
|---|---|---|---|
| `invalid_request` | 400 | campo fora das regras (nome de serviço fora de `^[a-z][a-z0-9-]{0,30}$`, `source.type` ou `build.builder` desconhecido, `healthcheck_path` sem `/` inicial, comando acima de 1024 caracteres), **`Idempotency-Key` malformada** (fora de 8 a 128 caracteres `[A-Za-z0-9._:-]`; a `message` diz a regra) ou `image` do deploy sem `@sha256:` | **corrigir** o campo ou o cabeçalho citado na `message` |
| `bad_json` | 400 | corpo JSON malformado | **corrigir** |
| `name_taken` | 409 | já existe recurso com esse nome (serviço, banco, variável duplicada no lote…); todo projeto nasce com um serviço `web` | **corrigir**: escolha outro nome, ou use o existente |
| `deploy_in_progress` | 409 | já há um deploy em andamento para o serviço | **esperar**: acompanhe o deploy em andamento (polling) em vez de disparar outro |
| `nothing_to_rollback` | 409 | o deploy de destino do rollback não terminou com sucesso | **corrigir**: escolha um deploy `ready` |
| `backup_not_ready` | 409 | restore de um backup que ainda não terminou | **esperar**: consulte o job do backup até `done` |
| `domain_taken` | 409 | domínio já adicionado ao time ou ativo em outro projeto | **corrigir**: use a prova TXT de posse ([Domínio próprio](/dominios/dominio-proprio/)) |
| `slug_taken` | 409 | o slug público já está em uso | **corrigir**: escolha outro slug |
| `idempotency_conflict` | 422 | mesma `Idempotency-Key` com pedido diferente (chave malformada: `400 invalid_request`) | **corrigir**: gere chave nova |

### Referências entre variáveis (`${{svc.VAR}}`, `${{shared.VAR}}`)

Aparecem quando as referências são resolvidas, no **deploy**: o `POST` do deploy (de serviço ou de grupo) responde `422` com `details.references` (lista de nomes) e nada é criado. Veja [Variáveis e referências](/conectados/variaveis-e-referencias/).

| Código | Significado | Ação |
|---|---|---|
| `422 unresolved_reference` | referência a variável ou serviço inexistente (a lista diz quais) | **corrigir**: crie a variável ou o serviço, ou corrija o nome; refaça o deploy |
| `422 reference_cycle` | `A` referencia `B` que referencia `A` | **corrigir**: quebre o ciclo (use valor literal em um dos lados) |
| `422 invalid_reference` | `${{...}}` fora da forma `escopo.VAR` | **corrigir** a sintaxe |
| `422 reference_too_deep` | cadeia de referências com mais de 16 níveis | **corrigir**: achate a cadeia |
| `422 value_too_large` | valor expandido acima de 64 KiB ou ambiente total acima de 256 KiB | **corrigir**: reduza o valor |
| `service_not_found` | o serviço citado na referência não está no projeto | **corrigir** o nome do serviço |

### Cotas

| Código | HTTP | Contexto | Ação |
|---|---|---|---|
| `quota_exceeded` | 413 | arquivos: cota de 5 GB / 10 000 arquivos do time | liberar espaço (`DELETE`) ou **parar** e pedir aumento |
| `quota_exceeded` | 422 | banco: cota de tamanho do banco (`quota_mb`) atingida; o banco fica `read_only` até baixar do limite | **parar** a escrita; apague dados ou **parar** e peça aumento de cota. Não repita a escrita |
| `project_quota_exceeded` | 409 | o time atingiu o limite de projetos | apague um projeto que não usa ou **parar** e peça aumento |

### Bancos gerenciados

| Código | HTTP | Significado | Ação |
|---|---|---|---|
| `unavailable` | 503 | o motor do banco (ou o nó) está indisponível | **repetir** com backoff (5, 10, 20, 40 s, até 5 vezes). Se persistir, **parar** e avisar |
| `backup_not_ready` | 409 | restore de um backup que ainda não terminou | aguarde o job do backup terminar (`GET .../jobs/{j}`) e refaça |
| `not_found` | 404 | banco, backup ou serviço inexistente ou de outro time | **corrigir** o id |

### Deploy: `failed` e `blocked`

Um deploy não devolve erro HTTP quando falha: ele termina no estado `failed` ou `blocked` ao consultar `GET .../deployments/{d}` (veja [Deploys e status](/operacao/deploys-e-status/)).

| Estado | Significado | Ação |
|---|---|---|
| `failed` | build, resolução de referências ou subida falhou | leia o log de build (`GET .../deployments/{d}/logs`) e o log de runtime (`GET .../services/{s}/logs`, escopo `logs:read`); **corrigir** a causa e dispare novo deploy |
| `blocked` | um serviço do qual este depende falhou no deploy de grupo | corrija o serviço que falhou primeiro; depois refaça o deploy do grupo |
| `failed` com `error.code = team_app_limit` | o time já roda o máximo de apps do plano (a mensagem diz "N de M") | pare ou remova um app, ou peça ao operador para ampliar o limite; depois repita o deploy |
| `failed` com `error.code = image_requires_root` | a imagem declara (ou assume) root; a plataforma a forçou a rodar como `65534` e o container encerrou nos primeiros 30 s | **corrigir**: declare `build.user` no serviço (`uid[:gid]`, nunca root) com um usuário que a imagem suporte, ou troque por uma variante sem privilégio; depois dispare novo deploy |
| `canceled`, `superseded` | substituído por um deploy mais novo | nada a fazer; acompanhe o mais novo |

### Limites de taxa e indisponibilidade

| Código | HTTP | Significado | Ação |
|---|---|---|---|
| `rate_limited` | 429 | limite por token ou IP; também o limite de deploys por hora do plano (`deploys_per_hour`) em deploy, rollback e deploy de grupo (o `Retry-After` é o tempo até abrir vaga) | **repetir** após `Retry-After` segundos; sem o cabeçalho, 30 s dobrando; reduza a concorrência. Veja [Rate limit e retry](/erros/rate-limit-e-retry/) |
| `unavailable` | 503 | serviço interno indisponível | **repetir** com backoff; persistindo, **parar** |

Respostas de exemplo (todas reproduzidas e conferidas pelo teste do guia):

```bash
curl -sS "$NEBULAR_API/v1/galleries" -H "Authorization: Bearer nbk_invalido"
```
```json
{"error": "unauthorized", "message": "Token de API ausente ou inválido."}
```

```bash
curl -sS "$NEBULAR_API/v1/galleries/nao-existe-0000/assets" -H "Authorization: Bearer $NEBULAR_TOKEN"
```
```json
{"error": "not_found", "message": "Recurso não encontrado."}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" \
  -F "file=@logo.png" -F "name=Logo Final.PNG"
```
```json
{"error": "invalid_request", "message": "Dados inválidos. Confira o nome e os campos enviados."}
```

```bash
# uma imagem acima do limite de megapixels
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@huge.png"
```
```json
{"error": "payload_too_large", "message": "O arquivo excede o limite de 25 MB."}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@photo.png.exe"
```
```json
{"error": "extension_mismatch", "message": "A extensão .png não corresponde ao conteúdo do arquivo."}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@app.exe"
```
```json
{"error": "class_not_allowed", "message": "Esta classe de arquivo não é permitida neste bucket."}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@eicar.bin"
```
```json
{"error": "malware_detected", "message": "Arquivo recusado: Win.Test.EICAR_HDB-1 detectado.", "signature": "Win.Test.EICAR_HDB-1"}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@unsafe.svg"
```
```json
{"error": "svg_unsafe", "message": "SVG recusado: contém <script>.", "reasons": ["script_element"]}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@locked.zip"
```
```json
{"error": "archive_encrypted", "message": "Arquivos compactados cifrados não podem ser varridos e são recusados."}
```

```bash
curl -sS -X POST "$NEBULAR_API/v1/galleries/$GALLERY/assets" \
  -H "Authorization: Bearer $NEBULAR_TOKEN" -F "file=@bomb.zip"
```
```json
{"error": "archive_bomb", "message": "Arquivo compactado recusado: excede os limites de expansão."}
```

Os demais códigos (`forbidden`, `quota_exceeded`, `unsupported_type`, `archive_unsupported`, `rate_limited`,
`unavailable`, `authz_unavailable`, `bad_json`, `unsupported_media_type`, `too_large`) têm o mesmo formato.
