Módulo API Pública (Integrações) — visão geral
Este módulo é a borda pública do Portal 2.0: a API que sistemas de fora do Portal (RIS/PACS de
terceiro, sistemas de parceiro, aplicações de integração) chamam para ler dados clínicos e
organizacionais. Ele é fundamentalmente diferente dos demais módulos desta documentação — que
descrevem a API consumida pelo próprio frontend do Portal. Aqui quem chama é uma aplicação, não
uma pessoa logada, e a credencial é um token de máquina (client_credentials), não uma sessão de
usuário.
O módulo vive em dois pacotes do backend (NestJS), com responsabilidades separadas:
integration-platform— o control plane: cadastro de adapters, instâncias de integração, segredos cifrados e concessão de rotas (capabilities). É onde um administrador configura o que uma integração pode fazer.public-api-edge— o data plane: as rotas HTTP que o parceiro efetivamente chama (/v1/exams,/v1/tenants,/v1/units) e a infraestrutura transversal que protege essas rotas (autenticação de máquina, resolução de capability, rate limit, formato de erro RFC 7807).
Por que dois pacotes. O control plane é administrado por uma pessoa autenticada no Portal (
JwtAuthenticationGuard, o mesmo token de sessão de usuário dos demais módulos). O data plane é chamado por uma aplicação externa autenticada por token de máquina (PublicApiMachineAuthGuard). Separar os pacotes impede que o serviço público (exposto à internet) carregue ou consiga invocar a escrita do control plane — o comentário no código é explícito: "o Edge não carrega — nem consegue chamar — a escrita do control plane" (integration-platform.module.ts).
Dois deployables, duas audiências
O mesmo pacote integration-platform é composto de duas formas diferentes em dois processos Nest
distintos:
| Deployable | Bootstrap | Porta (dev) | Quem chama | Guard |
|---|---|---|---|---|
API principal (app/main) | IntegrationPlatformModule.withControlPlane(...) | MAIN_API_PORT (default 3000) | Administrador de tenant/plataforma, autenticado no Portal | JwtAuthenticationGuard (sessão de usuário) |
API pública (app/public-api) | PublicApiEdgeModule.withDomainModules(...) + IntegrationPlatformModule.withDataPlaneAccess(...) | PUBLIC_API_PORT (default 3002) | Aplicação parceira (RIS/PACS/sistema externo) | PublicApiMachineAuthGuard (token de máquina) |
withDataPlaneAccess não registra nenhum controller — só expõe a porta
IntegrationPlatformPublicApi (leitura de autorização) e o RouteCapabilityDirectory para o
Edge consumir. Toda escrita administrativa (withControlPlane) só existe no processo da API
principal. Swagger da API pública: http://localhost:3002/docs (JSON em /docs-json).
Arquitetura
Autenticação: token de máquina, não sessão de usuário
Quem chama a API pública não faz login. A aplicação parceira recebe, no cadastro (feito pelo
administrador via Identity, fora deste módulo), um oauthClientId e um clientSecret, e troca os
dois pelo mesmo endpoint OAuth2 do Portal — POST /oauth/token com grant_type=client_credentials
— documentado em Token — API. O token resultante é um JWT de
máquina: carrega bindingType (PLATFORM/TENANT/UNIT), tenantId/unitId e escopos, mas
não representa nenhum usuário humano.
Em toda requisição à API pública, PublicApiMachineAuthGuard
(public-api-edge/shared/auth/public-api-machine-auth.guard.ts):
- Autentica o Bearer token via
IdentityMachineAccessApi.authenticateMachineAccessToken(implementado no módulo Identity — fora do escopo desta documentação). Um token de usuário comum, um esquemaBasic, um JWT malformado ou uma sessão de cliente já revogada são todos 401. - Lê a capability exigida pela rota (
@RequiresRouteCapability(...), um decorator por endpoint). Uma rota sem esse decorator falha comPUBLIC_API_ROUTE_CAPABILITY_MISSING(403) — nenhuma rota nova entra no ar sem declarar explicitamente que capability ela exige. - Chama
IntegrationPlatformPublicApi.resolveAuthorizedPassiveRoute({ capabilityKey, oauthClientId, unitId }), que resolve, nesta ordem: a integração ligada a esseoauthClientIdestá ativa → a capability existe no catálogo compilado → a instância tem um grant habilitado para essa capability → (se umunitIdfoi pedido na rota) esseunitIdestá dentro do alcance organizacional da instância. Qualquer falha nessa cadeia éINTEGRATION_PLATFORM_ROUTE_CAPABILITY_NOT_GRANTED(403) — a mensagem não distingue "grant ausente" de "grant desabilitado" de "instância não existe": deny-by-default sem vazar configuração de outro tenant. - Grava o resultado (
PublicApiMachineContext) na requisição — o controller da rota nunca repete essa resolução.
Ver Autenticação e autorização de máquina para o detalhamento completo, incluindo rate limit por perfil e o formato de erro.
Superfície de dados hoje
A API pública expõe, hoje, apenas leitura:
| Domínio | Rotas | Página |
|---|---|---|
| Exames | GET /v1/exams, GET /v1/exams/:examId | Exames |
| Tenants e unidades | GET /v1/tenants, GET /v1/tenants/:tenantId, GET /v1/tenants/:tenantId/units, GET /v1/units/:unitId, GET /v1/units/:unitId/modules | Tenants e unidades |
A confirmar — responsável: time de Integration Platform; data: 24/09/2026. O catálogo compilado de capabilities (
RouteCapabilityCatalog) já declara chaves de escrita sem rota correspondente implementada em nenhum controller encontrado neste levantamento:public-api.organization.tenant.create,public-api.organization.tenant.update,public-api.organization.unit.create,public-api.organization.unit.update,public-api.exam.update,public-api.report.readepublic-api.report.update. São capabilities reservadas para rotas futuras — nenhuma delas é chamável hoje. Ver Concessões de rota.
Como uma integração é montada (control plane)
Uma integração só atende requisições depois de quatro passos administrativos, todos via API principal (porta 3000, sessão de usuário):
- Cadastrar o adapter (opcional, reaproveitável entre instâncias) e publicar uma versão — Adapters.
- Criar a instância de integração, com um vínculo organizacional (
PLATFORM,TENANTouUNIT) e, quando fizer sentido, um segredo cifrado por alias — Instâncias de integração e Segredos cifrados. - Conceder as capabilities que essa instância pode usar — Concessões de rota.
- Ativar a instância — só então ela responde no data plane.
Compatibilidade com a API legada
O Portal 1.0 expôs, por anos, uma API pública em Express (mm-pacs-public-api) consumida por
integradores como Clinux, MRIS, MV, OneLaudos, Philips Tasy, Pixeon Arya, Pixxel, Qure, Sarah e
Totvs. O pacote public-api-edge mantém um inventário compilado dessas ~120 rotas antigas
(legacy-compatibility), cada uma já mapeada para a capability canônica que deve substituí-la —
mas, no código atual, nenhuma delas está ativa: toda chamada a um caminho legado responde 404.
Ver Compatibilidade com a API legada.
A confirmar — responsável: time de Integration Platform; data: 24/09/2026. O pacote
external-integrationaparece importado emapp/main/main.module.tsjunto deste módulo. Ele não faz parte do escopo lido neste levantamento (que cobriu apenaspublic-api-edgeeintegration-platform) — não é possível confirmar aqui se ele sobrepõe, precede ou substitui parte do cadastro de "aplicações" descrito no Bitrix (F-002) para a API antiga.
Observabilidade e formato de erro
Toda resposta de erro da API pública segue RFC 7807 Problem Details
(application/problem+json), com type, title, status, code, detail, instance,
requestId e (quando há tracing distribuído) traceId — nunca stack trace. Toda requisição recebe
um X-Execution-Id e um X-Request-Id de correlação, e a API responde em pt-BR por padrão,
com Content-Language/Accept-Language para inglês e espanhol. Cada chamada é registrada em log
estruturado com o operationId canônico, a capability exigida e o integrationInstanceId — nunca
com o payload de negócio.
Páginas deste módulo
| Página | Cobre |
|---|---|
| Autenticação e autorização de máquina | Token de máquina, resolução de capability, rate limit por perfil, formato de erro |
| Adapters | POST /v1/integration-platform/adapters, publicação/aprovação/revogação de versão |
| Instâncias de integração | Ciclo de vida da instância, vínculo organizacional, vínculo de OAuth client e de versão de adapter |
| Segredos cifrados | Criação, rotação e revogação de segredo por alias — AES-256-GCM |
| Concessões de rota | Catálogo de capabilities, grants por instância, de-para declarativo de resposta |
| Exames | GET /v1/exams, GET /v1/exams/:examId |
| Tenants e unidades | GET /v1/tenants, GET /v1/tenants/:tenantId, .../units, GET /v1/units/:unitId, .../modules |
| Compatibilidade com a API legada | Inventário das ~120 rotas do Portal 1.0 e seu mapeamento (pendente) para a API atual |
:::tip OpenAPI
A documentação interativa da API pública (schemas + "Try it out") está disponível em /docs no
processo app/public-api (local: http://localhost:3002/docs). As rotas administrativas do
control plane aparecem no Swagger da API principal (local: http://localhost:3000/docs).
:::