# Contrato da API v1

**Versao:** 1  
**Status:** Normativo para implementacao  
**Ultima revisao:** 2026-06-19

> Este documento e o elo formal entre `api/` e `front-end/` no modo desacoplado. Alteracoes neste contrato exigem revisao antes de qualquer implementacao.

## 1. Escopo e fonte de verdade

- Este arquivo define o contrato alvo da API publica em `/api/v1`.
- O contrato deve guiar implementacao em `api/` e consumo em `front-end/`.
- O scaffold atual da API ainda nao esta aderente a todos os endpoints e envelopes descritos aqui. Enquanto houver divergencia, este documento continua sendo a fonte de verdade do comportamento esperado.
- O contrato cobre apenas leitura publica de conteudo. Endpoints administrativos, autenticados ou internos ficam fora de escopo.
- A definicao da camada CMS -> API que estabiliza esses payloads esta em [docs/contracts/cms-transform-layer.md](./cms-transform-layer.md).
- A segmentacao multi-site e multi-front-end complementar desta v1 esta definida em [docs/multi-site-segmentation.md](../multi-site-segmentation.md).

## 2. Envelope padrao de resposta JSON

Todas as respostas da v1 devem seguir o mesmo envelope.

### 2.1 Sucesso

```json
{
  "data": {},
  "meta": {
    "version": "1"
  },
  "error": null
}
```

### 2.2 Sucesso com paginacao

```json
{
  "data": [],
  "meta": {
    "version": "1",
    "pagination": {
      "total": 100,
      "page": 1,
      "per_page": 20,
      "total_pages": 5
    }
  },
  "error": null
}
```

### 2.3 Erro

```json
{
  "data": null,
  "meta": {
    "version": "1"
  },
  "error": {
    "code": "NOT_FOUND",
    "message": "Recurso nao encontrado."
  }
}
```

### 2.4 Codigos de erro padronizados

| HTTP | `error.code` | Quando usar |
|---|---|---|
| `400` | `BAD_REQUEST` | Parametros invalidos ou malformados |
| `401` | `UNAUTHORIZED` | Credencial ausente ou invalida em endpoint protegido |
| `403` | `FORBIDDEN` | Origem nao autorizada ou acesso negado |
| `404` | `NOT_FOUND` | Recurso inexistente ou nao publicado |
| `429` | `RATE_LIMITED` | Limite de requisicoes excedido |
| `500` | `INTERNAL_ERROR` | Falha interna inesperada |

## 3. Endpoints v1

Base URL contratada: `/api/v1`

### 3.1 `GET /api/v1/posts`

Lista posts publicados em ordem decrescente de publicacao.

**Autenticacao:** nenhuma

**Query parameters**

| Parametro | Tipo | Padrao | Descricao |
|---|---|---|---|
| `page` | integer | `1` | Numero da pagina |
| `per_page` | integer | `20` | Quantidade por pagina, maximo `100` |
| `category` | string | - | Filtra por slug de categoria |
| `tag` | string | - | Filtra por slug de tag |

**Resposta `200 OK`**

```json
{
  "data": [
    {
      "id": 1,
      "slug": "meu-primeiro-post",
      "title": "Meu Primeiro Post",
      "excerpt": "Resumo do post...",
      "content": "Conteudo completo em HTML...",
      "featured_image": {
        "id": 10,
        "url": "/uploads/2026/01/imagem.jpg",
        "alt": "Descricao da imagem"
      },
      "categories": [
        { "id": 1, "slug": "noticias", "name": "Noticias" }
      ],
      "tags": [
        { "id": 3, "slug": "tecnologia", "name": "Tecnologia" }
      ],
      "author": {
        "name": "Admin"
      },
      "published_at": "2026-01-15T10:00:00Z",
      "updated_at": "2026-01-16T08:30:00Z"
    }
  ],
  "meta": {
    "version": "1",
    "pagination": {
      "total": 42,
      "page": 1,
      "per_page": 20,
      "total_pages": 3
    }
  },
  "error": null
}
```

### 3.2 `GET /api/v1/posts/{slug}`

Retorna o detalhe completo de um post publicado.

**Autenticacao:** nenhuma

**Path parameters**

| Parametro | Tipo | Descricao |
|---|---|---|
| `slug` | string | Slug unico do post |

**Resposta `200 OK`**

```json
{
  "data": {
    "id": 1,
    "slug": "meu-primeiro-post",
    "title": "Meu Primeiro Post",
    "excerpt": "Resumo do post...",
    "content": "Conteudo completo em HTML...",
    "featured_image": {
      "id": 10,
      "url": "/uploads/2026/01/imagem.jpg",
      "alt": "Descricao da imagem"
    },
    "categories": [
      { "id": 1, "slug": "noticias", "name": "Noticias" }
    ],
    "tags": [
      { "id": 3, "slug": "tecnologia", "name": "Tecnologia" }
    ],
    "seo": {
      "title": "Meu Primeiro Post | Site",
      "description": "Meta description do post",
      "canonical": "https://example.com/meu-primeiro-post",
      "robots": "index,follow,max-image-preview:large",
      "og": {
        "title": "Meu Primeiro Post | Site",
        "description": "Meta description do post",
        "image": {
          "id": 10,
          "url": "/uploads/2026/01/imagem.jpg",
          "alt": "Descricao da imagem"
        }
      },
      "twitter": {
        "card": "summary_large_image",
        "site": "@handle"
      }
    },
    "author": {
      "name": "Admin"
    },
    "published_at": "2026-01-15T10:00:00Z",
    "updated_at": "2026-01-16T08:30:00Z"
  },
  "meta": {
    "version": "1"
  },
  "error": null
}
```

**Erros esperados:** `404 NOT_FOUND`

### 3.3 `GET /api/v1/pages/{slug}`

Retorna o detalhe de uma pagina estatica publicada.

**Autenticacao:** nenhuma

**Path parameters**

| Parametro | Tipo | Descricao |
|---|---|---|
| `slug` | string | Slug unico da pagina |

**Resposta `200 OK`**

```json
{
  "data": {
    "id": 5,
    "slug": "sobre",
    "title": "Sobre",
    "content": "Conteudo da pagina em HTML...",
    "seo": {
      "title": "Sobre | Site",
      "description": "Meta description da pagina",
      "canonical": "https://example.com/sobre",
      "robots": "index,follow,max-image-preview:large",
      "og": {
        "title": "Sobre | Site",
        "description": "Meta description da pagina",
        "image": {
          "id": 11,
          "url": "/uploads/2026/01/sobre-og.jpg",
          "alt": "Imagem social da pagina Sobre"
        }
      },
      "twitter": {
        "card": "summary_large_image",
        "site": "@handle"
      }
    },
    "updated_at": "2026-01-10T12:00:00Z"
  },
  "meta": {
    "version": "1"
  },
  "error": null
}
```

**Erros esperados:** `404 NOT_FOUND`

### 3.4 `GET /api/v1/categories`

Lista todas as categorias disponiveis.

**Autenticacao:** nenhuma

**Resposta `200 OK`**

```json
{
  "data": [
    {
      "id": 1,
      "slug": "noticias",
      "name": "Noticias",
      "description": "Ultimas noticias",
      "post_count": 15
    }
  ],
  "meta": {
    "version": "1"
  },
  "error": null
}
```

### 3.5 `GET /api/v1/tags`

Lista todas as tags disponiveis.

**Autenticacao:** nenhuma

**Resposta `200 OK`**

```json
{
  "data": [
    {
      "id": 3,
      "slug": "tecnologia",
      "name": "Tecnologia",
      "post_count": 8
    }
  ],
  "meta": {
    "version": "1"
  },
  "error": null
}
```

### 3.6 `GET /api/v1/media/{id}`

Retorna os metadados de um asset de midia por ID.

**Autenticacao:** nenhuma

**Path parameters**

| Parametro | Tipo | Descricao |
|---|---|---|
| `id` | integer | Identificador do asset |

**Resposta `200 OK`**

```json
{
  "data": {
    "id": 10,
    "filename": "imagem.jpg",
    "url": "/uploads/2026/01/imagem.jpg",
    "mime_type": "image/jpeg",
    "size": 204800,
    "width": 1200,
    "height": 630,
    "alt": "Descricao alternativa da imagem",
    "uploaded_at": "2026-01-15T09:00:00Z"
  },
  "meta": {
    "version": "1"
  },
  "error": null
}
```

**Erros esperados:** `404 NOT_FOUND`

### 3.7 `GET /api/v1/site`

Retorna configuracoes globais do site e SEO padrao.

**Autenticacao:** nenhuma

**Resposta `200 OK`**

```json
{
  "data": {
    "name": "Nome do Site",
    "description": "Descricao do site",
    "url": "https://example.com",
    "language": "pt-BR",
    "logo": {
      "id": 1,
      "url": "/uploads/logo.png",
      "alt": "Logo do Site"
    },
    "favicon": "/uploads/favicon.ico",
    "seo": {
      "default_title": "Nome do Site",
      "title_separator": " | ",
      "default_description": "Descricao padrao do site",
      "default_robots": "index,follow,max-image-preview:large",
      "default_og_image": {
        "id": 1,
        "url": "/uploads/og-default.png",
        "alt": "Imagem institucional do site"
      }
    },
    "social": {
      "twitter": "@handle",
      "facebook": "https://facebook.com/page"
    }
  },
  "meta": {
    "version": "1"
  },
  "error": null
}
```

### 3.8 `GET /api/v1/menus/{location}`

Retorna a arvore de menu para uma localizacao registrada.

**Autenticacao:** nenhuma

**Path parameters**

| Parametro | Tipo | Descricao |
|---|---|---|
| `location` | string | Ex.: `primary`, `footer` |

**Resposta `200 OK`**

```json
{
  "data": {
    "location": "primary",
    "items": [
      {
        "id": 1,
        "label": "Home",
        "url": "/",
        "target": "_self",
        "order": 1,
        "children": []
      },
      {
        "id": 2,
        "label": "Blog",
        "url": "/blog",
        "target": "_self",
        "order": 2,
        "children": [
          {
            "id": 3,
            "label": "Tecnologia",
            "url": "/blog/categoria/tecnologia",
            "target": "_self",
            "order": 1,
            "children": []
          }
        ]
      }
    ]
  },
  "meta": {
    "version": "1"
  },
  "error": null
}
```

**Erros esperados:** `404 NOT_FOUND`

## 4. Regras do contrato

### 4.1 Versionamento

- Todo endpoint publico desta versao deve ser prefixado com `/api/v1/`.
- `meta.version` deve retornar `"1"` em toda resposta v1.
- Mudancas incompativeis exigem nova versao sob `/api/v2/`.
- Campos adicionais compativeis em `meta`, como contexto de `site` e `frontend`, sao permitidos em `v1`.
- A raiz `/api/` nao e endpoint de produto; ela pode expor apenas healthcheck ou metadados do serviço e nunca substitui contratos versionados.
- Recursos devem manter nomes estaveis e orientados ao dominio publico (`posts`, `pages`, `categories`, `tags`, `media`, `site`, `menus`). Nomes internos ou genericos como `content` e `taxonomies` nao contam como contrato externo final.

### 4.2 Autenticacao

- Os endpoints descritos neste documento sao publicos e nao exigem autenticacao.
- Endpoints fora de escopo, como operacoes administrativas, podem exigir credenciais proprias.
- O front-end nao deve depender de cookies de sessao do CMS para consumir a API publica.

### 4.3 CORS

- Apenas origens explicitamente autorizadas devem acessar a API via browser.
- Em desenvolvimento, a liberacao de `localhost` pode ser feita por configuracao de ambiente.
- Requisicoes nao autorizadas devem falhar com `403 FORBIDDEN`.

### 4.4 SEO

- `GET /api/v1/site` expoe os defaults necessarios para bootstrap de metadados no frontend.
- `GET /api/v1/posts/{slug}` e `GET /api/v1/pages/{slug}` devem retornar o objeto `seo` efetivo da rota, ja resolvido a partir da hierarquia site > destino > pagina.
- O payload efetivo deve incluir ao menos `title`, `description`, `canonical`, `robots`, `og` e `twitter`, para que o frontend nao reimplemente fallback editorial.
- O frontend deve sobrescrever os placeholders HTML estaticos assim que os dados da API estiverem disponiveis.

### 4.5 Rate limiting

- Limite contratual: `60` requisicoes por minuto por IP.
- Ao exceder o limite, a resposta deve ser `429 Too Many Requests`.
- Headers esperados:

```text
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1718827200
```

### 4.6 Cache

- Respostas publicas bem-sucedidas devem incluir:

```text
Cache-Control: public, max-age=60
```

- Respostas nao cacheaveis, como erros e endpoints privados, devem incluir:

```text
Cache-Control: no-store
```

### 4.7 Observabilidade

- Toda resposta deve incluir `X-Request-Id` com o identificador da requisicao.
- Respostas `5xx` devem manter o envelope do contrato e incluir `X-Request-Id`, sem expor stack trace, SQL ou segredos operacionais.
- O runtime da API deve produzir logs estruturados por requisicao com rota, metodo, status, latencia, IP resolvido e `request id`.
- Endpoints operacionais como `/api/health` e `/api/readiness` ficam fora do escopo funcional deste contrato, mas sao obrigatorios para operacao e monitoramento do servico.

## 5. Matriz de aderencia do scaffold atual

Esta matriz existe para separar decisao de contrato de implementacao incremental. O estado atual do scaffold nao redefine o contrato.

| Superficie | Estado atual | Contrato alvo |
|---|---|---|
| entrypoint `api/public/index.php` | responde health payload com `status`, `message`, `app`, `version` | manter apenas como raiz tecnica; nao substitui `/api/v1` |
| rotas de conteudo | `GET /v1/content` | `GET /api/v1/posts`, `GET /api/v1/posts/{slug}`, `GET /api/v1/pages/{slug}` |
| rotas de taxonomia | `GET /v1/taxonomies` | `GET /api/v1/categories`, `GET /api/v1/tags` |
| rotas de menu | `GET /v1/menus` | `GET /api/v1/menus/{location}` |
| rotas de midia | `GET /v1/media` | `GET /api/v1/media/{id}` |
| envelope JSON | arrays simples como `['resource' => '...']` | envelope padrao `data` + `meta` + `error` |
| middlewares | `CORS`, `RateLimit`, `Cache`, `Auth` sao stubs | implementar politicas descritas neste documento |
| cliente publico | `front-end/config/api.config.js` aponta para `/api/v1` e `front-end/src/api/endpoints.js` ja usa `posts`, `pages`, `categories` | manter o front-end acoplado ao contrato, nao ao scaffold temporario |

### 5.1 Implicacoes de implementacao

- A implementacao da fase 4 deve convergir o scaffold para os nomes e envelopes deste documento.
- Endpoints placeholder existentes podem ser removidos ou redirecionados antes de qualquer consumidor externo depender deles.
- Nenhum consumidor novo deve ser escrito contra `/v1/content`, `/v1/taxonomies` ou envelopes fora do padrao.

## 6. Politica de breaking changes

Breaking change e qualquer alteracao que force mudanca no consumidor para manter compatibilidade. Exemplos:

- remover campo existente
- renomear campo ou endpoint
- mudar tipo de dado de campo existente
- alterar a estrutura do envelope
- mudar o comportamento de parametro existente

Regras:

1. Breaking changes nao sao permitidas em `/api/v1/`.
2. Breaking changes devem nascer em `/api/v2/`.
3. Adicoes compativeis, como novos campos opcionais ou novos endpoints, podem entrar em `v1` apos revisao.
4. Mudancas neste arquivo exigem revisao antes da implementacao correspondente.

## 7. Estrategia operacional de versoes

### 7.1 Regras de evolucao

1. `v1` nasce quando os endpoints prioritarios deste documento estiverem funcionais com o envelope padrao.
2. A primeira publicacao externa da API deve anunciar apenas `/api/v1`; rotas placeholder anteriores nao devem ser divulgadas.
3. Campos novos opcionais podem ser adicionados em `v1` sem alterar `meta.version`.
4. Comportamentos experimentais devem entrar como endpoints novos ou query params opcionais; nao devem alterar comportamento existente de forma silenciosa.

### 7.2 Processo para `v2`

1. Criar novo documento de contrato, por exemplo `docs/contracts/api-v2.md`.
2. Publicar novos endpoints sob `/api/v2/...` sem remover `/api/v1/...`.
3. Definir janela de coexistencia entre versoes antes de qualquer desativacao.
4. Marcar a versao antiga como deprecated na documentacao e, quando aplicavel, via headers HTTP.
5. Somente remover `v1` depois que todos os consumidores conhecidos migrarem.

### 7.3 Sinais de deprecacao recomendados

- Header `Deprecation: true`
- Header `Sunset: <HTTP-date>`
- Nota de migracao no documento da versao antiga apontando para a nova

## 8. Contrato CMS -> API

A `api/` acessa o MySQL do `cms/` em modo somente leitura. Esse contrato existe para proteger o desacoplamento entre publicacao e consumo.

### 8.1 Regras obrigatorias

| Regra | Detalhe |
|---|---|
| leitura publica apenas | A API expoe apenas conteudo publicado e publico |
| sem escrita | A API nao faz `INSERT`, `UPDATE`, `DELETE` nem executa migracoes no banco do CMS |
| conexao dedicada | O acesso deve passar por uma abstracao de leitura, como `ReadConnection` |
| sem acoplamento de runtime | `api/` nao deve importar arquivos de runtime do `cms/core/` |
| schema coordenado | Mudancas de schema que afetem leitura da API contam como breaking change do contrato |

### 8.2 Fluxo de dados

```text
cms/       -> escreve no MySQL
api/       -> le do MySQL em modo read-only
api/       -> transforma dados internos em payloads estaveis via docs/contracts/cms-transform-layer.md
front-end/ -> consome apenas os payloads contratados em /api/v1
```

### 8.3 Proibicoes explicitas

- `front-end/` nao acessa banco diretamente
- `api/` nao compartilha sessao de administracao do CMS com o cliente publico
- `cms/` nao renderiza o front-end publico no modo desacoplado
- `api/` nao pode tratar schema interno como contrato publico sem passar por este documento

## 9. Arquivos relacionados

```text
docs/
  architecture.md
  front-end-public-shell.md
  contracts/
    api-v1.md
    cms-transform-layer.md

api/
  src/database/ReadConnection.php
  src/middleware/CORS.php
  src/middleware/RateLimit.php
  src/middleware/Cache.php
  src/response/JsonResponse.php
  src/response/ErrorResponse.php

front-end/
  config/api.config.js
  src/api/client.js
  src/api/endpoints.js
```

## 8. Nota de aderencia atual

- `front-end/src/api/endpoints.js` e `front-end/config/api.config.js` ja apontam para a base `/api/v1` e para recursos como `posts`, `pages` e `categories`.
- `api/public/index.php` agora publica os endpoints documentados neste contrato com envelope uniforme, CORS configuravel, rate limiting por IP e `Cache-Control` para respostas publicas.
- O catalogo publico padrao do repositario fica em `api/data/content.php`, permitindo que varios consumidores integrem contra um payload estavel antes da ligacao com o schema editorial definitivo do CMS.
- O shell publicado em `front-end/public/` centraliza o consumo desses endpoints e trata roteamento cliente, SEO e estados de falha sem depender do CMS.
