# Deploys (referência)

## POST `/v1/projects/{p}/deploy`

Deploy do projeto (grupo)

Implanta vários serviços em **ordem de dependência** (`${{svc.VAR}}`), em camadas: a camada N só começa quando a N-1 está `ready`; dependente de um que falhou fica `blocked`. `services` vazio ou omitido = todos os serviços de aplicação **que têm o que implantar**: o serviço sem fonte (p.ex. o `web` de um projeto novo, sem upload nem git/imagem) entra no grupo como item `skipped`, com `reason`, e não faz o grupo falhar; um grupo em que tudo é `skipped` termina `ready`. Listar o serviço em `services` não o pula: sem fonte, o deploy dele falha. Faça polling em `GET …/group-deployments/{g}`. Referências inexistentes ou em ciclo são recusadas antes de criar qualquer coisa (`422`). Conta um deployment por serviço no limite por hora do time (`429 rate_limited` + `Retry-After`).

Escopo exigido: `deploy`.

Escopo exigido: `deploy` · operationId: `deployGroup`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `Idempotency-Key` | header | string | não | Chave de idempotência (8 a 128 caracteres `[A-Za-z0-9._:-]`; use um UUID). Mesma chave + mesmo pedido em 24 h ⇒ a mesma resposta, com `Idempotent-Replay: true`; mesma chave + pedido diferente ⇒ `422 idempotency_conflict`. Falhas nunca são gravadas, então a repetição executa de verdade. |

### Corpo (application/json)

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `services` | array<string> | não | Ids ou nomes (até 100); vazio ou omitido = todos os serviços de aplicação. |

### Resposta 202

Deploy de grupo aceito.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `status` | enum(running, ready, failed, canceled, superseded) | sim |  |
| `error` | string | não | Motivo, quando `failed`. |
| `items` | array<object> | sim |  |
| `items[].service_id` | string | sim |  |
| `items[].deployment_id` | string | não | Ausente para itens `blocked` e `skipped`. |
| `items[].status` | enum(queued, building, deploying, ready, failed, blocked, canceled, skipped) | sim | Status do deployment do item, `blocked` (dependência falhou) ou `skipped` (serviço sem fonte para implantar; veja `reason`). |
| `items[].reason` | string | não | Motivo, presente só nos itens `skipped`. |
| `items[].layer` | integer | sim | Camada de dependência (0 = sem dependências). |

```json
{
  "id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "status": "running",
  "items": [
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a9z",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
      "status": "ready",
      "layer": 0
    },
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a8k",
      "status": "building",
      "layer": 1
    }
  ]
}
```

### Resposta 422

`unresolved_reference`, `reference_cycle` ou `idempotency_conflict`.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

```json
{
  "error": "unresolved_reference",
  "message": "Há referências para variáveis ou serviços que não existem.",
  "details": {
    "references": [
      "db.MYSQL_HOST"
    ]
  }
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `400` Entrada inválida (nome de arquivo fora das regras, cursor malformado, formulário sem `file`, nome de galeria vazio ou longo); `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `413` Corpo JSON acima do limite da rota (`too_large`); `415` O corpo precisa ser `application/json` (`unsupported_media_type`); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## GET `/v1/projects/{p}/group-deployments/{g}`

Consulta um deploy de grupo (polling)

Estado do grupo e de cada serviço (`items`, com a `layer`).

Escopo exigido: `projects:read`.

Escopo exigido: `projects:read` · operationId: `getGroupDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `g` | path | string | sim | Id do deploy de grupo. |

### Resposta 200

Grupo.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `status` | enum(running, ready, failed, canceled, superseded) | sim |  |
| `error` | string | não | Motivo, quando `failed`. |
| `items` | array<object> | sim |  |
| `items[].service_id` | string | sim |  |
| `items[].deployment_id` | string | não | Ausente para itens `blocked` e `skipped`. |
| `items[].status` | enum(queued, building, deploying, ready, failed, blocked, canceled, skipped) | sim | Status do deployment do item, `blocked` (dependência falhou) ou `skipped` (serviço sem fonte para implantar; veja `reason`). |
| `items[].reason` | string | não | Motivo, presente só nos itens `skipped`. |
| `items[].layer` | integer | sim | Camada de dependência (0 = sem dependências). |

```json
{
  "id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "status": "running",
  "items": [
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a9z",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
      "status": "ready",
      "layer": 0
    },
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a8k",
      "status": "building",
      "layer": 1
    }
  ]
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## POST `/v1/projects/{p}/group-deployments/{g}/cancel`

Cancela um deploy de grupo

Cancela o que ainda não terminou. Um grupo finalizado volta inalterado.

Escopo exigido: `deploy`.

Escopo exigido: `deploy` · operationId: `cancelGroupDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `g` | path | string | sim | Id do deploy de grupo. |

### Resposta 202

Grupo (cancelado ou inalterado).

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `status` | enum(running, ready, failed, canceled, superseded) | sim |  |
| `error` | string | não | Motivo, quando `failed`. |
| `items` | array<object> | sim |  |
| `items[].service_id` | string | sim |  |
| `items[].deployment_id` | string | não | Ausente para itens `blocked` e `skipped`. |
| `items[].status` | enum(queued, building, deploying, ready, failed, blocked, canceled, skipped) | sim | Status do deployment do item, `blocked` (dependência falhou) ou `skipped` (serviço sem fonte para implantar; veja `reason`). |
| `items[].reason` | string | não | Motivo, presente só nos itens `skipped`. |
| `items[].layer` | integer | sim | Camada de dependência (0 = sem dependências). |

```json
{
  "id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "status": "running",
  "items": [
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a9z",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
      "status": "ready",
      "layer": 0
    },
    {
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "deployment_id": "01jb7k5a5q8r7t2v4w6x8y0a8k",
      "status": "building",
      "layer": 1
    }
  ]
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## GET `/v1/projects/{p}/services/{s}/deployments`

Lista os deployments do serviço

Deployments do mais novo para o mais antigo.

Escopo exigido: `projects:read`.

Escopo exigido: `projects:read` · operationId: `listDeployments`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `s` | path | string | sim | Id ou nome do serviço dentro do projeto. |
| `cursor` | query | string | não | Cursor opaco devolvido em `next_cursor` da página anterior. |
| `limit` | query | integer | não | Itens por página (1 a 200). |

### Resposta 200

Página (mais novo/antigo primeiro conforme o recurso).

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `next_cursor` | string\|null | sim | Passe em `cursor` para a próxima página; `null` ao fim. |
| `has_more` | boolean | sim | Há mais páginas. |
| `items` | array<object> | sim |  |
| `items[].id` | string | sim |  |
| `items[].service_id` | string | sim |  |
| `items[].group_deployment_id` | string | não |  |
| `items[].status` | enum(queued, building, deploying, ready, failed, blocked, canceled, superseded) | sim | `canceled` e `deploying` podem ainda aparecer como `failed`/`building` em instalações antigas. |
| `items[].trigger` | enum(api, git, dash, rollback, template, manual) | sim | Quem disparou. `manual` é o que o servidor devolve para disparos feitos pelo painel quando a origem não foi registrada. |
| `items[].source_ref` | string | não | SHA/branch (git) ou referência da imagem. |
| `items[].image_digest` | string | não |  |
| `items[].error` | object | não |  |
| `items[].error.code` | string | sim | Código estável da falha: `deploy_failed` (genérico), `unresolved_reference`, `reference_cycle`, `image_requires_root`, `crashed_after_start` e os do build: `build_clone_failed`, `build_detect_failed`, `build_install_failed`, `build_script_failed`, `build_copy_failed`, `build_activate_failed`, `activate_readonly_write` (o app tentou gravar sob `/app`, somente leitura: use um volume nessa pasta ou `/tmp`), `activate_app_crashed` (o app encerrou logo ao iniciar; a mensagem traz a primeira linha de erro do log), `activate_oom` (o app usou mais memória que o limite do serviço e foi encerrado), `activate_no_http` (o app estável e rodando não respondeu na porta esperada), `team_app_limit` (o time já roda o máximo de apps do plano; a mensagem traz N de M: pare ou remova um app, ou peça ao operador para ampliar o limite), `build_volume_unavailable`, `build_oom`, `build_timeout`, `build_storage_error` (falha interna de armazenamento da plataforma, não do repositório), `build_disk_full` (espaço de build cheio) e `build_storage_unavailable` (nenhum armazenamento de build utilizável na plataforma; não é falta de espaço). O código vem gravado com o deploy; `message` traz a causa em português (sem segredos nem caminhos do servidor). |
| `items[].error.message` | string | sim |  |
| `items[].error.details` | any | não | Dados extras do erro (qualquer JSON). |
| `items[].created_at` | string | sim |  |
| `items[].started_at` | string | não |  |
| `items[].finished_at` | string | não |  |

```json
{
  "items": [
    {
      "id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
      "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "group_deployment_id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
      "status": "building",
      "trigger": "api",
      "source_ref": "main",
      "created_at": "2026-10-10T14:03:11Z",
      "started_at": "2026-10-10T14:03:11Z"
    }
  ],
  "next_cursor": "MDFqYjdrNWE1cThyN3QydjR3NnU4eTBhM2Q",
  "has_more": true
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `400` Entrada inválida (nome de arquivo fora das regras, cursor malformado, formulário sem `file`, nome de galeria vazio ou longo); `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## POST `/v1/projects/{p}/services/{s}/deployments`

Dispara um deploy do serviço

Dispara um deployment do serviço e devolve `202` com o deployment (o trabalho é assíncrono: faça polling em `GET …/deployments/{d}`).
Corpo: `{}` (último commit da branch ou a imagem do serviço), `{"ref":"<sha|branch>"}` (git), `{"image":"repo@sha256:…"}` (imagem), ou **multipart** com o campo `file` (tar.gz ou zip, até 512 MB) para enviar o código-fonte.
Referências `${{svc.VAR}}` são validadas no disparo: inexistentes ou em ciclo ⇒ `422 unresolved_reference` / `reference_cycle` com `details.references`; se só aparecerem no build, ficam em `deployment.error`.

Estados: `queued → building → deploying → ready`, ou `failed` / `blocked` / `canceled` / `superseded`. Faça polling com backoff (comece em 2 s, dobre até 15 s). Não há webhooks neste ciclo.
**Limite por hora:** cada time pode criar no máximo N deployments (todos os projetos somados) em uma janela deslizante de 1 hora (`GET /v1/limits` → `deploys_per_hour`); acima disso, `429 rate_limited` com `Retry-After` (segundos até liberar uma vaga).
Imagem com `USER root` roda como `65534`; se o container encerrar nos primeiros 30 s depois de iniciar, o deployment falha com `error.code=image_requires_root` e a mensagem pede para declarar `build.user` (uid[:gid], nunca root) no serviço. Corpo ausente (sem `Content-Type`) vale `{}`; com corpo, é JSON (ou multipart). Armazenamento de builds da plataforma cheio ou indisponível ⇒ `503 unavailable` com `details.deployment_id` (um deployment `failed` fica no histórico com `error.code` `build_disk_full` ou `build_storage_unavailable`) e `details.error_code`; repita em alguns minutos.

Escopo exigido: `deploy`.

Escopo exigido: `deploy` · operationId: `createDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `s` | path | string | sim | Id ou nome do serviço dentro do projeto. |
| `Idempotency-Key` | header | string | não | Chave de idempotência (8 a 128 caracteres `[A-Za-z0-9._:-]`; use um UUID). Mesma chave + mesmo pedido em 24 h ⇒ a mesma resposta, com `Idempotent-Replay: true`; mesma chave + pedido diferente ⇒ `422 idempotency_conflict`. Falhas nunca são gravadas, então a repetição executa de verdade. |

### Corpo (application/json)

### Corpo (multipart/form-data)

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `file` | string | sim | Arquivo `.tar.gz` ou `.zip` com o código. |

### Resposta 202

Deployment aceito.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `service_id` | string | sim |  |
| `group_deployment_id` | string | não |  |
| `status` | enum(queued, building, deploying, ready, failed, blocked, canceled, superseded) | sim | `canceled` e `deploying` podem ainda aparecer como `failed`/`building` em instalações antigas. |
| `trigger` | enum(api, git, dash, rollback, template, manual) | sim | Quem disparou. `manual` é o que o servidor devolve para disparos feitos pelo painel quando a origem não foi registrada. |
| `source_ref` | string | não | SHA/branch (git) ou referência da imagem. |
| `image_digest` | string | não |  |
| `error` | object | não |  |
| `error.code` | string | sim | Código estável da falha: `deploy_failed` (genérico), `unresolved_reference`, `reference_cycle`, `image_requires_root`, `crashed_after_start` e os do build: `build_clone_failed`, `build_detect_failed`, `build_install_failed`, `build_script_failed`, `build_copy_failed`, `build_activate_failed`, `activate_readonly_write` (o app tentou gravar sob `/app`, somente leitura: use um volume nessa pasta ou `/tmp`), `activate_app_crashed` (o app encerrou logo ao iniciar; a mensagem traz a primeira linha de erro do log), `activate_oom` (o app usou mais memória que o limite do serviço e foi encerrado), `activate_no_http` (o app estável e rodando não respondeu na porta esperada), `team_app_limit` (o time já roda o máximo de apps do plano; a mensagem traz N de M: pare ou remova um app, ou peça ao operador para ampliar o limite), `build_volume_unavailable`, `build_oom`, `build_timeout`, `build_storage_error` (falha interna de armazenamento da plataforma, não do repositório), `build_disk_full` (espaço de build cheio) e `build_storage_unavailable` (nenhum armazenamento de build utilizável na plataforma; não é falta de espaço). O código vem gravado com o deploy; `message` traz a causa em português (sem segredos nem caminhos do servidor). |
| `error.message` | string | sim |  |
| `error.details` | any | não | Dados extras do erro (qualquer JSON). |
| `created_at` | string | sim |  |
| `started_at` | string | não |  |
| `finished_at` | string | não |  |

```json
{
  "id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
  "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "group_deployment_id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "status": "building",
  "trigger": "api",
  "source_ref": "main",
  "created_at": "2026-10-10T14:03:11Z",
  "started_at": "2026-10-10T14:03:11Z"
}
```

### Resposta 409

`deploy_in_progress`: já há um deploy em andamento neste serviço.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

### Resposta 413

Código-fonte (multipart) acima do limite.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

### Resposta 422

`unresolved_reference`, `reference_cycle`, `invalid_reference`, `reference_too_deep`, `value_too_large` (com `details`) ou `idempotency_conflict`.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

```json
{
  "error": "unresolved_reference",
  "message": "Há referências para variáveis ou serviços que não existem.",
  "details": {
    "references": [
      "db.MYSQL_HOST"
    ]
  }
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `400` Entrada inválida (nome de arquivo fora das regras, cursor malformado, formulário sem `file`, nome de galeria vazio ou longo); `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `415` O corpo precisa ser `application/json` (`unsupported_media_type`); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## GET `/v1/projects/{p}/services/{s}/deployments/{d}`

Consulta um deployment (polling)

Estado atual do deployment. Quando `failed`, `error` traz `code`, `message` e `details`.

Escopo exigido: `projects:read`.

Escopo exigido: `projects:read` · operationId: `getDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `s` | path | string | sim | Id ou nome do serviço dentro do projeto. |
| `d` | path | string | sim | Id do deployment (do serviço da rota). |

### Resposta 200

Deployment.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `service_id` | string | sim |  |
| `group_deployment_id` | string | não |  |
| `status` | enum(queued, building, deploying, ready, failed, blocked, canceled, superseded) | sim | `canceled` e `deploying` podem ainda aparecer como `failed`/`building` em instalações antigas. |
| `trigger` | enum(api, git, dash, rollback, template, manual) | sim | Quem disparou. `manual` é o que o servidor devolve para disparos feitos pelo painel quando a origem não foi registrada. |
| `source_ref` | string | não | SHA/branch (git) ou referência da imagem. |
| `image_digest` | string | não |  |
| `error` | object | não |  |
| `error.code` | string | sim | Código estável da falha: `deploy_failed` (genérico), `unresolved_reference`, `reference_cycle`, `image_requires_root`, `crashed_after_start` e os do build: `build_clone_failed`, `build_detect_failed`, `build_install_failed`, `build_script_failed`, `build_copy_failed`, `build_activate_failed`, `activate_readonly_write` (o app tentou gravar sob `/app`, somente leitura: use um volume nessa pasta ou `/tmp`), `activate_app_crashed` (o app encerrou logo ao iniciar; a mensagem traz a primeira linha de erro do log), `activate_oom` (o app usou mais memória que o limite do serviço e foi encerrado), `activate_no_http` (o app estável e rodando não respondeu na porta esperada), `team_app_limit` (o time já roda o máximo de apps do plano; a mensagem traz N de M: pare ou remova um app, ou peça ao operador para ampliar o limite), `build_volume_unavailable`, `build_oom`, `build_timeout`, `build_storage_error` (falha interna de armazenamento da plataforma, não do repositório), `build_disk_full` (espaço de build cheio) e `build_storage_unavailable` (nenhum armazenamento de build utilizável na plataforma; não é falta de espaço). O código vem gravado com o deploy; `message` traz a causa em português (sem segredos nem caminhos do servidor). |
| `error.message` | string | sim |  |
| `error.details` | any | não | Dados extras do erro (qualquer JSON). |
| `created_at` | string | sim |  |
| `started_at` | string | não |  |
| `finished_at` | string | não |  |

```json
{
  "id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
  "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "group_deployment_id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "status": "building",
  "trigger": "api",
  "source_ref": "main",
  "created_at": "2026-10-10T14:03:11Z",
  "started_at": "2026-10-10T14:03:11Z"
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## POST `/v1/projects/{p}/services/{s}/deployments/{d}/cancel`

Cancela um deployment

Cancela um deployment `queued`/`building`/`deploying`. Um deployment já terminado volta inalterado. Só cancela jobs enfileirados no processo atual (`409 not_cancelable` nos demais).

Escopo exigido: `deploy`.

Escopo exigido: `deploy` · operationId: `cancelDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `s` | path | string | sim | Id ou nome do serviço dentro do projeto. |
| `d` | path | string | sim | Id do deployment (do serviço da rota). |

### Resposta 202

Deployment (cancelado ou inalterado).

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `service_id` | string | sim |  |
| `group_deployment_id` | string | não |  |
| `status` | enum(queued, building, deploying, ready, failed, blocked, canceled, superseded) | sim | `canceled` e `deploying` podem ainda aparecer como `failed`/`building` em instalações antigas. |
| `trigger` | enum(api, git, dash, rollback, template, manual) | sim | Quem disparou. `manual` é o que o servidor devolve para disparos feitos pelo painel quando a origem não foi registrada. |
| `source_ref` | string | não | SHA/branch (git) ou referência da imagem. |
| `image_digest` | string | não |  |
| `error` | object | não |  |
| `error.code` | string | sim | Código estável da falha: `deploy_failed` (genérico), `unresolved_reference`, `reference_cycle`, `image_requires_root`, `crashed_after_start` e os do build: `build_clone_failed`, `build_detect_failed`, `build_install_failed`, `build_script_failed`, `build_copy_failed`, `build_activate_failed`, `activate_readonly_write` (o app tentou gravar sob `/app`, somente leitura: use um volume nessa pasta ou `/tmp`), `activate_app_crashed` (o app encerrou logo ao iniciar; a mensagem traz a primeira linha de erro do log), `activate_oom` (o app usou mais memória que o limite do serviço e foi encerrado), `activate_no_http` (o app estável e rodando não respondeu na porta esperada), `team_app_limit` (o time já roda o máximo de apps do plano; a mensagem traz N de M: pare ou remova um app, ou peça ao operador para ampliar o limite), `build_volume_unavailable`, `build_oom`, `build_timeout`, `build_storage_error` (falha interna de armazenamento da plataforma, não do repositório), `build_disk_full` (espaço de build cheio) e `build_storage_unavailable` (nenhum armazenamento de build utilizável na plataforma; não é falta de espaço). O código vem gravado com o deploy; `message` traz a causa em português (sem segredos nem caminhos do servidor). |
| `error.message` | string | sim |  |
| `error.details` | any | não | Dados extras do erro (qualquer JSON). |
| `created_at` | string | sim |  |
| `started_at` | string | não |  |
| `finished_at` | string | não |  |

```json
{
  "id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
  "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "group_deployment_id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "status": "building",
  "trigger": "api",
  "source_ref": "main",
  "created_at": "2026-10-10T14:03:11Z",
  "started_at": "2026-10-10T14:03:11Z"
}
```

### Resposta 409

`not_cancelable`.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.

## POST `/v1/projects/{p}/services/{s}/deployments/{d}/rollback`

Volta para um deployment anterior

Cria um **novo** deployment (`trigger=rollback`) que reimplanta `{d}` com as variáveis dele. Corpo: `{}` ou vazio. Conta no limite de deployments por hora do time (`429 rate_limited` + `Retry-After`).

Escopo exigido: `deploy`.

Escopo exigido: `deploy` · operationId: `rollbackDeployment`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `p` | path | string | sim | Id (26 caracteres) ou slug do projeto. Projeto de outro time ou fora dos `project_ids` do token ⇒ `404`. |
| `s` | path | string | sim | Id ou nome do serviço dentro do projeto. |
| `d` | path | string | sim | Id do deployment (do serviço da rota). |
| `Idempotency-Key` | header | string | não | Chave de idempotência (8 a 128 caracteres `[A-Za-z0-9._:-]`; use um UUID). Mesma chave + mesmo pedido em 24 h ⇒ a mesma resposta, com `Idempotent-Replay: true`; mesma chave + pedido diferente ⇒ `422 idempotency_conflict`. Falhas nunca são gravadas, então a repetição executa de verdade. |

### Resposta 202

Rollback aceito (novo deployment).

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `service_id` | string | sim |  |
| `group_deployment_id` | string | não |  |
| `status` | enum(queued, building, deploying, ready, failed, blocked, canceled, superseded) | sim | `canceled` e `deploying` podem ainda aparecer como `failed`/`building` em instalações antigas. |
| `trigger` | enum(api, git, dash, rollback, template, manual) | sim | Quem disparou. `manual` é o que o servidor devolve para disparos feitos pelo painel quando a origem não foi registrada. |
| `source_ref` | string | não | SHA/branch (git) ou referência da imagem. |
| `image_digest` | string | não |  |
| `error` | object | não |  |
| `error.code` | string | sim | Código estável da falha: `deploy_failed` (genérico), `unresolved_reference`, `reference_cycle`, `image_requires_root`, `crashed_after_start` e os do build: `build_clone_failed`, `build_detect_failed`, `build_install_failed`, `build_script_failed`, `build_copy_failed`, `build_activate_failed`, `activate_readonly_write` (o app tentou gravar sob `/app`, somente leitura: use um volume nessa pasta ou `/tmp`), `activate_app_crashed` (o app encerrou logo ao iniciar; a mensagem traz a primeira linha de erro do log), `activate_oom` (o app usou mais memória que o limite do serviço e foi encerrado), `activate_no_http` (o app estável e rodando não respondeu na porta esperada), `team_app_limit` (o time já roda o máximo de apps do plano; a mensagem traz N de M: pare ou remova um app, ou peça ao operador para ampliar o limite), `build_volume_unavailable`, `build_oom`, `build_timeout`, `build_storage_error` (falha interna de armazenamento da plataforma, não do repositório), `build_disk_full` (espaço de build cheio) e `build_storage_unavailable` (nenhum armazenamento de build utilizável na plataforma; não é falta de espaço). O código vem gravado com o deploy; `message` traz a causa em português (sem segredos nem caminhos do servidor). |
| `error.message` | string | sim |  |
| `error.details` | any | não | Dados extras do erro (qualquer JSON). |
| `created_at` | string | sim |  |
| `started_at` | string | não |  |
| `finished_at` | string | não |  |

```json
{
  "id": "01jb7k5a5q8r7t2v4w6x8y0a3d",
  "service_id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "group_deployment_id": "01jb7k7a5q8r7t2v4w6x8y0a5f",
  "status": "building",
  "trigger": "api",
  "source_ref": "main",
  "created_at": "2026-10-10T14:03:11Z",
  "started_at": "2026-10-10T14:03:11Z"
}
```

### Resposta 409

`nothing_to_rollback` ou `deploy_in_progress`.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

### Resposta 422

`idempotency_conflict`.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `error` | enum(unauthorized, forbidden, not_found, payload_too_large, quota_exceeded, unsupported_type, class_not_allowed, extension_mismatch, malware_detected, svg_unsafe, archive_encrypted, archive_bomb, archive_unsupported, rate_limited, unavailable, idempotency_conflict, invalid_request, unsupported_media_type, bad_json, too_large, authz_unavailable, database_unavailable, restore_not_removable, gallery_not_empty, bad_body, internal, name_taken, slug_taken, project_quota_exceeded, project_not_empty, deploy_in_progress, nothing_to_rollback, not_cancelable, sensitive_lowered, duplicate_variable, domain_taken, volume_exists, volume_in_use, volume_attached, backup_not_ready, unresolved_reference, reference_cycle, invalid_reference, reference_too_deep, value_too_large) | sim | Código estável para tratamento programático. A tabela completa está na descrição geral. |
| `message` | string | sim | Mensagem legível em pt-BR. |
| `details` | object | não | Presente em alguns `422` (ex.: `unresolved_reference` e `reference_cycle` trazem `details.references`). |
| `details.references` | array<string> | não | Referências problemáticas (só nomes, nunca valores). |
| `details.retry_after_seconds` | integer | não | Em `422 rate_limited` (ex.: verificação manual de domínio): segundos até poder tentar de novo. |

```json
{
  "error": "idempotency_conflict",
  "message": "Esta Idempotency-Key já foi usada com outro conteúdo. Gere uma chave nova."
}
```

### Erros comuns

Corpo `{"error", "message", "details"?}` (veja [Autenticação e visão geral](/referencia/autenticacao/) e a [tabela de erros](/erros/tabela/)): `400` Entrada inválida (nome de arquivo fora das regras, cursor malformado, formulário sem `file`, nome de galeria vazio ou longo); `401` Token ausente, inválido, expirado ou revogado. A resposta é genérica de propósito; `403` O token não tem o escopo exigido pela operação; `404` Recurso inexistente ou pertencente a outro time (indistinguíveis de propósito); `429` Limite de requisições excedido. Espere `Retry-After` segundos; `500` Falha interna (`internal`). O detalhe fica só no log do servidor; repita depois, e informe o `Idempotency-Key` ao suporte se persistir; `503` Serviço (ex.: scanner ou borda) temporariamente indisponível. Tente de novo após `Retry-After`.
