# 06 - APIs

> A API REST do Genesis vive no Sinal `Plataforma/Api` (`componentes/Missoes/Plataforma/sinais/Api/`). Ela é **interna**: o consumidor é o próprio frontend do Genesis (mesma origem). Existem dois grupos de rotas: `/api/v1` (versionado, autenticado) e `/api/webhooks` (inbound de sistemas externos, autenticado por HMAC).
>
> Relacionados: [01 - Arquitetura Geral](01-arquitetura-geral.md) (Laboratórios que a API delega), [08 - Integrações](08-integracoes.md) (webhooks), [10 - Guia de Desenvolvimento](10-guia-de-desenvolvimento.md#como-criar-um-endpoint) (passo a passo).

## 1. Por que existe uma API REST se o framework tem `dadosG`?

O framework Galaxia tem seu próprio mecanismo de chamadas internas (Laboratórios + `dadosG`). A API REST foi criada por três motivos:

1. **Frontends novos** (dashboards, apps mobile-first como a edição de rota do motorista) precisam de JSON limpo com contrato estável, não do envelope HTML/Luz do Galaxia.
2. **Acesso sem sessão Galaxia** — o motorista que edita rota acessa por token público + OTP, sem login.
3. **Sistemas externos** (WMS, provedores de mensageria) precisam de um endpoint inbound com autenticação por HMAC.

A regra de ouro se mantém: **handler não tem regra de negócio** — ele valida, delega a um Laboratório de domínio e adapta a resposta.

## 2. Arquitetura do pipeline

```mermaid
flowchart LR
    A[system/index.php\nCoffeeCode Router] --> B[Routes.php\ngrupo /api/v1]
    B --> C[Api.php\ncontroller fino]
    C --> D[Dispatcher.php]
    D --> M1[AuditLog before]
    M1 --> M2[ValidacaoConteudo]
    M2 --> M3[RateLimit]
    M3 --> M4[AuthJwt]
    M4 --> M5[DevAccess se requireDevAccess]
    M5 --> H[Handler::handle]
    H --> L[Laboratório de domínio]
    L --> R[Response::send\n+ AuditLog after]
```

Pontos-chave (referências na data de escrita):

- `system/index.php:26` instancia o router; `system/index.php:38` inclui `Routes.php` **antes** das rotas catch-all do Galaxia — a ordem importa: se mover para depois, as rotas dinâmicas `{p1}/{p2}` sequestram `/api/v1/*`.
- `Routes.php:14` fixa o namespace `GalaxiaMissoes\Plataforma\sinais\Api`; toda rota resolve para um método da classe `Api`.
- Cada método de `Api.php` só chama `$this->dispatcher()->run(verbo, pattern, Handler::class, $data, $opts)`. Opções: `skipAuth`, `skipRateLimit`, `skipValidacaoConteudo`, `requireDevAccess`.
- `Dispatcher::run()` (`Dispatcher.php:21`) executa a cadeia de middlewares na ordem do diagrama. **Rate limit vem antes da auth** de propósito — para não gastar validação de JWT com quem está estourando limite. Middleware que retorna `false` curto-circuita; o audit roda **sempre** (mesmo em erro 500).
- `Bootstrap::ensure()` (`Dispatcher.php:28`) registra canais OTP, eventos e callbacks WMS de forma idempotente antes de qualquer handler — provedores reais são registrados antes do fallback `LogCanal` para não serem sobrescritos.

## 3. Autenticação

### 3.1 As três formas de identificar o chamador

`AuthJwtMiddleware` tenta, em cascata:

1. **`Authorization: Bearer <jwt>`** — JWT HS256 assinado com a constante `PASSDECODELOGIN` (definida em `system/base_config/Core/MainBoth.php:81`). Claims obrigatórios: `user` e `customer`.
2. **`?token=<jwt>`** ou `body['token']` — compatibilidade com chamadas legadas.
3. **Sessão Galaxia** — lê `getSession('galaxiaUserInfo')` e monta os claims com `auth_via='session'`. **Este é o caminho normal do frontend interno** (mesma origem, cookie de sessão).

Sem nenhuma das três → `auth_missing` HTTP 401.

**A API não emite tokens.** Ela reutiliza o JWT do fluxo de login Galaxia; a mesma chave estática valida tudo. Consequências que você precisa saber:

- Não há checagem própria de expiração — só existe se o token tiver claim `exp` (a lib Firebase\JWT lança nesse caso).
- `user_ip`, `user_os`, `user_browser` são **registrados mas não verificados** (sem IP-binding).

### 3.2 Rotas de desenvolvedor

`/api/v1/docs` (Swagger UI) e `/api/v1/openapi.json` usam `skipAuth` + `requireDevAccess`. O `DevAccessMiddleware` resolve o usuário (sessão → sessão legada → JWT) e compara com `Config::DEV_USER_IDS` (`Config.php:20`, default `[1]`). Override sem tocar código: `define('GENESIS_API_DEV_USERS', '1,2,7')`.

### 3.3 Rotas públicas

- `GET /api/v1/health` — sem auth, sem rate limit (healthcheck).
- `POST /api/webhooks/{channel}` — sem JWT; autenticação por **HMAC** (ver §6).
- Endpoints do fluxo do motorista (`/edicao-rota/{token}/...`) — o `{token}` público + OTP fazem o papel de autenticação de domínio.

## 4. Convenções de request/response

### 4.1 Envelopes

```json
// Sucesso (BaseHandler::ok)
{ "data": { ... }, "request_id": "uuid" }

// Erro (BaseHandler::erro)
{ "error": { "code": "slug_do_erro", "message": "texto" }, "request_id": "uuid" }
```

O header `X-Request-Id` é sempre ecoado (aceito do cliente ou gerado como UUIDv4). Slugs de erro padronizados: `auth_missing`, `auth_invalid`, `auth_required`, `forbidden_dev_only`, `invalid_input` (422), `operation_failed` (400), `unauthorized`, `not_found`, `unsupported_media_type` (415), `invalid_json` (400), `payload_too_large` (413), `rate_limit_exceeded` (429), `internal_error` (500), `unknown_channel`, `hmac_mismatch`, `misconfigured_target`, `lab_failure`.

### 4.2 Ponte com o framework legado

Muitos Laboratórios respondem no formato `Message::render()` do Galaxia (`notification_class`, `notification_html`, `status: ok|no`). `BaseHandler::adaptarRespostaLegacy()` traduz isso para o envelope REST:

- `notification_class = error` → `operation_failed` 400; `warning` → `invalid_input` 422.
- No sucesso, remove os metadados `notification_*` e o `status` — **mas só quando `status` é `'ok'`/`'no'`**. Status de domínio (ex.: `aguardando_envio` do OTP) é preservado de propósito.

### 4.3 Validação de conteúdo

Só para POST/PUT/PATCH: body > 1 MB → 413; `Content-Type` ≠ JSON → 415; JSON inválido → 400. Body JSON é lido de `php://input`; sem `Content-Type: application/json`, cai em `$_POST`.

### 4.4 Rate limit

Janela fixa de 1 minuto: 60 req/usuário autenticado, 10 req/IP anônimo, por endpoint. **Fail-open**: se a Sonda de rate limit falhar (infra), a chamada passa e o erro é só logado — decisão consciente para a API interna nunca cair por causa do contador.

### 4.5 Paginação

Não existe paginação genérica. Há limites pontuais: `GET /api/v1/edicao-rota?ids=` aceita no máximo **50 ids** (excedentes → 422; ids inexistentes são omitidos em silêncio). O dashboard usa o padrão `?light=1` para respostas rápidas sem séries pesadas (ver [07 - Front-end](07-front-end.md)).

## 5. Inventário de endpoints

### Sistema / documentação

| Método | Path | Finalidade |
|---|---|---|
| GET | `/api/v1/health` | Healthcheck (status, versão, hora). Público. |
| GET | `/api/v1/openapi.json` | Spec OpenAPI 3.0.3 (dev-only). Fallback estático em `OpenApi/spec.php`. |
| GET | `/api/v1/docs` | Swagger UI (dev-only). |

### Edição de rota (motorista via token público / gestor autenticado)

Handlers em `Handlers/EdicaoRota/`, delegando a `Logistica\sinais\Edicaorota\Laboratorios\*`. Fluxo de negócio no [02 - Fluxos](02-fluxos-do-sistema.md).

| Método | Path | Finalidade |
|---|---|---|
| POST | `/api/v1/edicao-rota` | Cria uma edição de rota (gera token para o motorista). |
| POST | `/api/v1/edicao-rota/{token}/otp/gerar` | Gera/envia OTP ao motorista. `canal=exibir_tela` devolve o código em claro (`codigo_exibido`). |
| POST | `/api/v1/edicao-rota/{token}/otp/validar` | Valida o OTP e abre a sessão de edição. |
| POST | `/api/v1/edicao-rota/{token}/rascunho` | Salva rascunho da rota proposta. |
| POST | `/api/v1/edicao-rota/{token}/proposta` | Submete a proposta final do motorista. |
| POST | `/api/v1/edicao-rota/{id}/aprovar` | Gestor aprova (injeta `respondido_por` do JWT/sessão). |
| POST | `/api/v1/edicao-rota/{id}/rejeitar` | Gestor rejeita. |
| POST | `/api/v1/edicao-rota/{id}/cancelar` | Cancela a edição. |
| GET | `/api/v1/edicao-rota?ids=1,2,3` | Consulta em lote (máx. 50), para polling do painel do gestor. |
| GET | `/api/v1/edicao-rota/{id}` | Consulta por id (gestor). |
| GET | `/api/v1/edicao-rota/token/{token}` | Consulta por token (motorista). |

### OTP (Mensageria)

Handlers em `Handlers/Otp/`, delegando a `Mensageria\sinais\Otp\Laboratorios\*`.

| Método | Path | Finalidade |
|---|---|---|
| POST | `/api/v1/otp` | Cria **e** envia OTP pelo canal configurado. |
| POST | `/api/v1/otp/criar` | Cria sem enviar; devolve `data.codigo` em claro (uso interno; o audit log redacta). |
| GET | `/api/v1/otp/{id}` | Consulta estado do OTP. |
| POST | `/api/v1/otp/validar` | Valida um código. |
| POST | `/api/v1/otp/{id}/reenviar` | Reenvia. |
| POST | `/api/v1/otp/{id}/cancelar` | Cancela. |

### Dashboard operacional de logística (todos GET)

~45 endpoints sob `/api/v1/dashboard/...`, todos delegando a `Logistica\sinais\Dashboard\Laboratorios\*`. Filtros comuns por query string (`from`, `to`, `tipo_separacao`...). Grupos:

- **KPIs e fluxo:** `kpis` (suporta `?light=1`), `tipos-missao`, `fluxo-diario`, `fluxo-horario`, `overnight-serie`, `overnight/romaneios`, `lead-time/romaneios`, `expedicao/viagens-por-tipo`.
- **Rankings:** `ranking-separadores`, `ranking-conferentes`, `ranking-conferentes/mais-lentos`.
- **Erros de separação:** `erros-separacao` (+ `/comparativo`, `/por-pessoa`, `/itens`), família `erros-separador/*` (por separador, itens, faltantes por separador/produto).
- **Erros de carregamento:** `erros-carregamento` (+ `/comparativo`, `/itens`), `erros-por-conferente`, `carregamentos/detalhe-hora`.
- **Tempos e retrabalho:** `tempo-medio-item`, `tempo-medio-missao`, `tempo-medio-carregamento`, `retrabalho` (+ `/itens`).
- **Pesagem:** `pesagem/kpis`, `/ranking-operadores`, `/por-hora`, `/itens-do-dia`, `/top-produtos`, `/tempo-por-sku`, `/mapa-calor`.
- **Séries e timelines:** `separacao/itens-por-hora`, `separacao/cubagem-por-hora`, `separacao/timeline`, `separacao/top-produtos`, `usuario/top-produtos`, `carregamento/timeline`, `visao-geral/timeline`.

O inventário completo com handler por rota está em `Routes.php:45-89` — trate o arquivo como fonte da verdade; esta lista é o mapa.

### Regras (motor de regras por cliente)

| Método | Path | Finalidade |
|---|---|---|
| GET | `/api/v1/regras?modulo=` | Lista regras do `customer` da sessão + registry de situações disponíveis. |
| POST | `/api/v1/regras` | Cria/atualiza regra (validada por `SalvarRegraValidator`; escopo por tenant). |
| DELETE | `/api/v1/regras/{id}` | Remove regra. |

### Veículo

| Método | Path | Finalidade |
|---|---|---|
| GET | `/api/v1/veiculo/{id}/motorista-padrao` | Telefone/email padrão do motorista do veículo (404 se não existe). |
| PATCH | `/api/v1/veiculo/{id}/motorista-padrao` | Salva/limpa contato padrão (422 em validação). |

### Webhook inbound

| Método | Path | Finalidade |
|---|---|---|
| POST | `/api/webhooks/{channel}` | Recebe eventos de sistemas externos (WMS etc.). Sem JWT — HMAC. Ver §6. |

## 6. Webhook inbound genérico

O endpoint `/api/webhooks/{channel}` é a **porta única de entrada** para sistemas externos. Cada integração é uma linha na tabela `webhook_endpoints` (Sonda `WebhookEndpointSonda`), com: `channel` (slug único), `secret`, `signature_header` (default `X-Signature`), `signature_algo` (`hmac_sha256`/`hmac_sha1`), `target_lab` (FQCN do Laboratório que processa) e `payload_mapper` (normalizador opcional).

Fluxo (`Handlers/Webhooks/InboundHandler.php`):

1. Busca o canal — lookup **global**, com multi-tenant desligado de propósito (a request externa não tem sessão; a identidade É o channel, e o tenant vem das colunas da linha). Canal inexistente/inativo → `unknown_channel` 404.
2. Valida HMAC sobre o **rawBody** com `hash_equals` (timing-safe). Aceita formato `sha256=<hex>` ou hex puro. Falha → `hmac_mismatch` 401.
3. Se houver `payload_mapper` implementando `PayloadNormalizer`, traduz o payload externo para o formato interno (ex.: `IntegracaoWms/Webhooks/WmsPayloadNormalizer`).
4. Instancia `new $targetLab($payload, $endpoint)` — o `$endpoint` autenticado é a fonte da verdade do tenant; Laboratórios multi-tenant **devem** usar isso, não a sessão.
5. Responde `{ received: true, channel, request_id, ... }` (200, ou 422 se o lab retornar `ok: false`).

Para registrar uma integração nova **não se escreve rota nova** — insere-se uma linha em `webhook_endpoints` e cria-se o Laboratório alvo. Ver [08 - Integrações](08-integracoes.md).

## 7. Auditoria

`AuditLogMiddleware` grava toda chamada em `api_audit_log` (Sonda sem tenant control, para auditar também chamadas anônimas), com duração, status e payload **redactado**: campos `codigo`, `codigo_otp`, `password`, `senha`, `secret`, `token` são mascarados recursivamente; payloads são truncados em 64 KB. Roda inclusive quando o handler explode com 500.

## 8. Gotchas específicos da API

1. **Não reordenar os includes em `system/index.php`** — `/api/v1` precisa vir antes das rotas dinâmicas do Galaxia.
2. **`POST /otp/criar` e `canal=exibir_tela` devolvem código em claro por design.** O caller é responsável por não vazar. O `ExibirTelaCanal` usa estado estático (`reset()` antes, leitura depois) — não é seguro entre requisições concorrentes no mesmo processo.
3. **`DEV_USER_IDS` default é `[1]`** — sem `GENESIS_API_DEV_USERS` definido, só o usuário 1 acessa `/docs` (o resto recebe 403 `forbidden_dev_only`).
4. **Rate limit é fail-open e de janela fixa** — bordas de janela permitem burst de até 2× o limite.
5. **Webhook: linhas com `block` NULL são excluídas** do lookup (`active=1 AND block>0`) — canal "sumindo" geralmente é `block` errado.
6. **`adaptarRespostaLegacy` preserva `status` de domínio** — não assuma que `status` sempre some do payload de sucesso.
