# Serviços (referência)

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

Lista os serviços do projeto

Serviços do projeto, do mais antigo para o mais novo. `status` e `needs_redeploy` são derivados do estado atual.

Escopo exigido: `projects:read`.

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

| 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`. |
| `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[].project_id` | string | sim |  |
| `items[].name` | string | sim | Nome único no projeto (`^[a-z][a-z0-9-]{0,30}$`). |
| `items[].kind` | enum(app, database) | sim |  |
| `items[].engine` | enum(postgres, mariadb, sqlite) | não | Só `kind=database`. |
| `items[].visibility` | enum(public, private) | sim |  |
| `items[].slug` | string | não | Subdomínio público (`visibility=public`). |
| `items[].port` | integer | sim |  |
| `items[].source` | object | sim |  |
| `items[].source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `items[].source.repo` | string | não | `git`: `dono/repositório`. |
| `items[].source.branch` | string | não | `git`: branch implantada. |
| `items[].source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `items[].source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `items[].source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `items[].build` | object | sim |  |
| `items[].build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `items[].healthcheck_path` | string | não |  |
| `items[].start_command` | string | não |  |
| `items[].build_command` | string | não |  |
| `items[].resources` | object | sim |  |
| `items[].resources.memory_mb` | integer | sim |  |
| `items[].resources.cpus` | number | sim |  |
| `items[].auto_deploy` | boolean | sim |  |
| `items[].needs_redeploy` | boolean | sim | Variáveis ou configuração mudaram desde o último deploy. |
| `items[].status` | enum(idle, deploying, running, failed) | sim | Derivado do deployment mais recente. |
| `items[].public_url` | string | não |  |
| `items[].depends_on` | array<string> | sim | Nomes dos serviços referenciados por `${{svc.VAR}}`. |
| `items[].created_at` | string | sim |  |

```json
{
  "items": [
    {
      "id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
      "name": "web",
      "kind": "app",
      "visibility": "public",
      "slug": "loja",
      "port": 3000,
      "source": {
        "type": "git",
        "repo": "acme/loja",
        "branch": "main"
      },
      "build": {
        "builder": "auto"
      },
      "healthcheck_path": "/health",
      "resources": {
        "memory_mb": 512,
        "cpus": 0.5
      },
      "auto_deploy": true,
      "needs_redeploy": false,
      "status": "running",
      "public_url": "https://loja.nebularapps.com",
      "depends_on": [
        "db"
      ],
      "created_at": "2026-10-10T14:03:11Z"
    }
  ],
  "next_cursor": null,
  "has_more": false
}
```

### 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`

Cria um serviço de aplicação

Cria um serviço `kind=app`. Bancos nascem em `POST …/databases`. `visibility` omitido = `private` (fail-safe); serviço público exige `slug` e serviço privado não pode ter `slug`. Padrões: `port=3000`, 512 MB / 0,5 vCPU, `source={"type":"upload"}`, `build={"builder":"auto"}`.

Escopo exigido: `projects:write`.

Escopo exigido: `projects:write` · operationId: `createService`

| 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 |
|---|---|---|---|
| `name` | string | sim |  |
| `kind` | enum(app) | não | Só `app` (bancos nascem em `/databases`). |
| `visibility` | enum(public, private) | não | Padrão `private` quando omitido (nada fica público por omissão). `private` não pode ter `slug`. Em `public` o `slug` é opcional: omitido, a plataforma usa o nome do serviço se estiver livre (e não reservado), senão `<nome>-<4 caracteres aleatórios>`; o `slug` efetivo vem na resposta. |
| `slug` | string | não | Opcional quando `visibility=public` (derivado do nome do serviço se omitido; um slug explícito já em uso dá `409 slug_taken`). |
| `source` | object | não |  |
| `source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `source.repo` | string | não | `git`: `dono/repositório`. |
| `source.branch` | string | não | `git`: branch implantada. |
| `source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `build` | object | não |  |
| `build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `port` | integer | não |  |
| `healthcheck_path` | string | não |  |
| `start_command` | string | não |  |
| `build_command` | string | não |  |
| `resources` | object | não |  |
| `resources.memory_mb` | integer | sim |  |
| `resources.cpus` | number | sim |  |
| `auto_deploy` | boolean | não |  |

### Resposta 201

Serviço criado.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `name` | string | sim | Nome único no projeto (`^[a-z][a-z0-9-]{0,30}$`). |
| `kind` | enum(app, database) | sim |  |
| `engine` | enum(postgres, mariadb, sqlite) | não | Só `kind=database`. |
| `visibility` | enum(public, private) | sim |  |
| `slug` | string | não | Subdomínio público (`visibility=public`). |
| `port` | integer | sim |  |
| `source` | object | sim |  |
| `source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `source.repo` | string | não | `git`: `dono/repositório`. |
| `source.branch` | string | não | `git`: branch implantada. |
| `source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `build` | object | sim |  |
| `build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `healthcheck_path` | string | não |  |
| `start_command` | string | não |  |
| `build_command` | string | não |  |
| `resources` | object | sim |  |
| `resources.memory_mb` | integer | sim |  |
| `resources.cpus` | number | sim |  |
| `auto_deploy` | boolean | sim |  |
| `needs_redeploy` | boolean | sim | Variáveis ou configuração mudaram desde o último deploy. |
| `status` | enum(idle, deploying, running, failed) | sim | Derivado do deployment mais recente. |
| `public_url` | string | não |  |
| `depends_on` | array<string> | sim | Nomes dos serviços referenciados por `${{svc.VAR}}`. |
| `created_at` | string | sim |  |

```json
{
  "id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "name": "web",
  "kind": "app",
  "visibility": "public",
  "slug": "loja",
  "port": 3000,
  "source": {
    "type": "git",
    "repo": "acme/loja",
    "branch": "main"
  },
  "build": {
    "builder": "auto"
  },
  "healthcheck_path": "/health",
  "resources": {
    "memory_mb": 512,
    "cpus": 0.5
  },
  "auto_deploy": true,
  "needs_redeploy": false,
  "status": "running",
  "public_url": "https://loja.nebularapps.com",
  "depends_on": [
    "db"
  ],
  "created_at": "2026-10-10T14:03:11Z"
}
```

### Resposta 409

`name_taken` ou `slug_taken`.

| 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": "name_taken",
  "message": "Já existe um serviço com esse nome no projeto."
}
```

### Resposta 422

`quota_exceeded` (limite de serviços do plano) 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": "quota_exceeded",
  "message": "Limite de serviços do plano atingido."
}
```

### 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}/services/{s}`

Detalhe do serviço

Devolve o serviço. `{s}` aceita o id ou o nome.

Escopo exigido: `projects:read`.

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

| 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. |

### Resposta 200

Serviço.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `name` | string | sim | Nome único no projeto (`^[a-z][a-z0-9-]{0,30}$`). |
| `kind` | enum(app, database) | sim |  |
| `engine` | enum(postgres, mariadb, sqlite) | não | Só `kind=database`. |
| `visibility` | enum(public, private) | sim |  |
| `slug` | string | não | Subdomínio público (`visibility=public`). |
| `port` | integer | sim |  |
| `source` | object | sim |  |
| `source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `source.repo` | string | não | `git`: `dono/repositório`. |
| `source.branch` | string | não | `git`: branch implantada. |
| `source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `build` | object | sim |  |
| `build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `healthcheck_path` | string | não |  |
| `start_command` | string | não |  |
| `build_command` | string | não |  |
| `resources` | object | sim |  |
| `resources.memory_mb` | integer | sim |  |
| `resources.cpus` | number | sim |  |
| `auto_deploy` | boolean | sim |  |
| `needs_redeploy` | boolean | sim | Variáveis ou configuração mudaram desde o último deploy. |
| `status` | enum(idle, deploying, running, failed) | sim | Derivado do deployment mais recente. |
| `public_url` | string | não |  |
| `depends_on` | array<string> | sim | Nomes dos serviços referenciados por `${{svc.VAR}}`. |
| `created_at` | string | sim |  |

```json
{
  "id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "name": "web",
  "kind": "app",
  "visibility": "public",
  "slug": "loja",
  "port": 3000,
  "source": {
    "type": "git",
    "repo": "acme/loja",
    "branch": "main"
  },
  "build": {
    "builder": "auto"
  },
  "healthcheck_path": "/health",
  "resources": {
    "memory_mb": 512,
    "cpus": 0.5
  },
  "auto_deploy": true,
  "needs_redeploy": false,
  "status": "running",
  "public_url": "https://loja.nebularapps.com",
  "depends_on": [
    "db"
  ],
  "created_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`.

## PATCH `/v1/projects/{p}/services/{s}`

Atualiza o serviço

Atualização parcial; o que for omitido não muda. Mudar configuração marca o serviço como `needs_redeploy`.

Escopo exigido: `projects:write`.

Escopo exigido: `projects:write` · operationId: `updateService`

| 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. |

### Corpo (application/json)

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `visibility` | enum(public, private) | não |  |
| `slug` | string | não |  |
| `source` | object | não |  |
| `source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `source.repo` | string | não | `git`: `dono/repositório`. |
| `source.branch` | string | não | `git`: branch implantada. |
| `source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `build` | object | não |  |
| `build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `port` | integer | não |  |
| `healthcheck_path` | string | não |  |
| `start_command` | string | não |  |
| `build_command` | string | não |  |
| `resources` | object | não |  |
| `resources.memory_mb` | integer | sim |  |
| `resources.cpus` | number | sim |  |
| `auto_deploy` | boolean | não |  |

### Resposta 200

Serviço atualizado.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `id` | string | sim |  |
| `project_id` | string | sim |  |
| `name` | string | sim | Nome único no projeto (`^[a-z][a-z0-9-]{0,30}$`). |
| `kind` | enum(app, database) | sim |  |
| `engine` | enum(postgres, mariadb, sqlite) | não | Só `kind=database`. |
| `visibility` | enum(public, private) | sim |  |
| `slug` | string | não | Subdomínio público (`visibility=public`). |
| `port` | integer | sim |  |
| `source` | object | sim |  |
| `source.type` | enum(upload, git, image, database) | sim | De onde vem o código. |
| `source.repo` | string | não | `git`: `dono/repositório`. |
| `source.branch` | string | não | `git`: branch implantada. |
| `source.root_dir` | string | não | `git`: subdiretório do monorepo. |
| `source.watch_paths` | array<string> | não | `git`: globs que disparam deploy automático. |
| `source.ref` | string | não | `image`: `repo[:tag]@sha256:<digest>`. |
| `build` | object | sim |  |
| `build.builder` | enum(auto, node, go, rust, python, static, dockerfile, none) | sim | Como construir. |
| `healthcheck_path` | string | não |  |
| `start_command` | string | não |  |
| `build_command` | string | não |  |
| `resources` | object | sim |  |
| `resources.memory_mb` | integer | sim |  |
| `resources.cpus` | number | sim |  |
| `auto_deploy` | boolean | sim |  |
| `needs_redeploy` | boolean | sim | Variáveis ou configuração mudaram desde o último deploy. |
| `status` | enum(idle, deploying, running, failed) | sim | Derivado do deployment mais recente. |
| `public_url` | string | não |  |
| `depends_on` | array<string> | sim | Nomes dos serviços referenciados por `${{svc.VAR}}`. |
| `created_at` | string | sim |  |

```json
{
  "id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "name": "web",
  "kind": "app",
  "visibility": "public",
  "slug": "loja",
  "port": 3000,
  "source": {
    "type": "git",
    "repo": "acme/loja",
    "branch": "main"
  },
  "build": {
    "builder": "auto"
  },
  "healthcheck_path": "/health",
  "resources": {
    "memory_mb": 512,
    "cpus": 0.5
  },
  "auto_deploy": true,
  "needs_redeploy": false,
  "status": "running",
  "public_url": "https://loja.nebularapps.com",
  "depends_on": [
    "db"
  ],
  "created_at": "2026-10-10T14:03:11Z"
}
```

### Resposta 409

`slug_taken`.

| 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/)): `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`.

## DELETE `/v1/projects/{p}/services/{s}`

Apaga o serviço

Apaga o serviço com deployments, variáveis e domínios. Serviço com **volume** exige apagar o volume antes (`409 volume_attached`). Idempotente (repetir ⇒ `204`).

Escopo exigido: `projects:write`.

Escopo exigido: `projects:write` · operationId: `deleteService`

| 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. |

### Resposta 204

Removido (ou já removido anteriormente: repetir é `204`).

### Resposta 409

`volume_attached`: apague o volume primeiro.

| 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`.
