# Templates (referência)

## GET `/v1/templates`

Catálogo de templates

Templates disponíveis, ordenados por id. Cada um traz os `params` aceitos e sua versão.

Escopo exigido: `templates:deploy`.

Escopo exigido: `templates:deploy` · operationId: `listTemplates`

| Parâmetro | Em | Tipo | Obrig. | Descrição |
|---|---|---|---|---|
| `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[].name` | string | sim |  |
| `items[].version` | integer | sim |  |
| `items[].params` | array<object> | sim |  |
| `items[].params[].name` | string | sim |  |
| `items[].params[].type` | string | sim |  |
| `items[].params[].default` | any | não | Valor padrão (qualquer JSON). |

```json
{
  "items": [
    {
      "id": "wordpress",
      "name": "WordPress",
      "version": 1,
      "params": [
        {
          "name": "table_prefix",
          "type": "string",
          "default": "wp_"
        }
      ]
    }
  ],
  "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; `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}/templates/{t}`

Instancia um template no projeto

Cria **atomicamente** os serviços do template (falhou ⇒ desfaz o que criou) e inicia o deploy de grupo dos apps (`deploy_started`). Se o deploy não pôde iniciar, `deploy_error` diz por quê e os serviços continuam criados. Idempotente pela `Idempotency-Key`. O serviço `web` que todo projeto traz é **adotado** pelo template que declara um `web`, se ainda estiver virgem (sem deploy, variáveis, domínio ou volume); qualquer outro nome já em uso, ou um `web` já usado, ⇒ `409 name_taken`. Sem bancos ou volumes habilitados na instalação ⇒ `503`.

Escopo exigido: `templates:deploy`.

Escopo exigido: `templates:deploy` · operationId: `instantiateTemplate`

| 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`. |
| `t` | path | string | sim | Id do template (`GET /v1/templates`). |
| `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 |
|---|---|---|---|
| `params` | object | não | Valores dos parâmetros do template: texto, número ou booleano. |

### Resposta 201

Template instanciado.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `template_id` | string | sim |  |
| `version` | integer | sim |  |
| `project_id` | string | sim |  |
| `services` | array<object> | sim |  |
| `services[].id` | string | sim |  |
| `services[].name` | string | sim |  |
| `services[].kind` | enum(app, database) | sim |  |
| `deploy_started` | boolean | sim | O deploy de grupo dos apps foi iniciado. |
| `deploy_error` | string | não | Por que o deploy não iniciou (os serviços continuam criados). Consulte `group-deployments`. |

```json
{
  "template_id": "wordpress",
  "version": 1,
  "project_id": "01jb7k3m5q8r7t2v4w6x8y0a1b",
  "services": [
    {
      "id": "01jb7k4a5q8r7t2v4w6x8y0a9z",
      "name": "db",
      "kind": "database"
    },
    {
      "id": "01jb7k4a5q8r7t2v4w6x8y0a2c",
      "name": "web",
      "kind": "app"
    }
  ],
  "deploy_started": true
}
```

### Resposta 409

`name_taken`: já existe serviço com o nome de um serviço do template.

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