Portal do Cliente
O Portal do Cliente é a área da WebApiAlcance consumida pelo cliente final da Alcance Contabilidade (a empresa atendida), e não por operadores internos. Ele agrega três routers, todos montados sob o prefixo /portal:
- Sessão & Perfil / Gestão de acessos (
routes.py) - login do portal, dados do usuário logado e o CRUD administrativo de acessos. - Dados (
portal_data_routes.py) - lançamentos de lucro e sócios (QSA) da empresa do cliente. - Plano de Saúde (
portal_plano_saude_routes.py) - operadoras vinculadas, dependentes e lançamentos de plano de saúde.
A marca registrada de todo o portal é o isolamento por id_cliente: o identificador da empresa vem sempre do token e nunca do path/query/body. Assim o usuário do cliente A jamais enxerga (ou grava) dados do cliente B - o padrão anti-IDOR aplicado em cada rota.
Realm separado - API Keys do MCP NÃO acessam o portal
O Portal do Cliente é um realm de autenticação totalmente separado da autenticação interna da WebApi. Ele não usa o sistema de escopos recurso:acao das API Keys, não emite um Principal interno e não é alcançável pelas API Keys do gateway MCP. O acesso às rotas de dados e perfil é feito por um token do portal (JWT emitido no login do cliente, carregando id_cliente) validado por get_current_portal_user. Uma API Key interna - mesmo global - não autentica nenhuma rota protegida por get_current_portal_user.
Base path
/api/v1/portalA URL completa em produção é https://api.contabilidadealcance.com.br/api/v1/portal.
Autenticação
O portal tem duas proteções distintas, dependendo de quem chama a rota:
-
Token do portal -
get_current_portal_user. É o mecanismo padrão do realm. O cliente faz login emPOST /portal/auth/token(JSON comidentifier+password) e recebe um JWT que carrega oid_clienteda empresa. Esse token protegeGET /portal/auth/mee todas as rotas de dados (lucro, sócios, plano de saúde, dependentes, operadoras). Não há escoposrecurso:acao; a autorização é o próprio vínculoid_clientedo token.POST /auth/tokené público (sem token), porém protegido pelo mesmo rate limit do/auth/tokeninterno. -
Perfil interno -
require_perfil("administrador", "diretoria"). As rotas de gestão de acessos (/portal/users) são operadas pelo Hub interno e exigem umPrincipalinterno com perfil RBACadministradoroudiretoria- não um token do portal. São elas que provisionam/editam/desativam os acessos que depois farão login no portal.
Sem escopos recurso:acao
Nenhuma rota do portal usa os escopos recurso:acao. As rotas de cliente usam o token do portal (get_current_portal_user); as rotas administrativas de /portal/users usam require_perfil("administrador", "diretoria") (Principal interno). Consulte Autenticação para o realm interno.
Endpoints
Todas as respostas (exceto o login) seguem o envelope padrão { "success": true, "message": "...", "data": {...} }. Quando uma listagem não encontra registros, o portal responde com error_response - { "success": false, "message": "...", "details": [] }.
Como o portal não usa escopos recurso:acao, cada endpoint indica a Proteção aplicável: público (não exige API Key), token do portal (get_current_portal_user) ou perfil interno administrador/diretoria (require_perfil).
Sessão & Perfil
Router routes.py - login do portal, perfil do usuário logado e gestão administrativa de acessos.
POST /portal/auth/token
Login do cliente no portal. Aceita login OU e-mail no mesmo campo identifier + password, tudo em JSON (diferente do /auth/token interno, que é form-urlencoded/OAuth2). Protegido pelo rate limit portal_auth_token.
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - as credenciais vão no corpo da requisição (PortalLoginRequest): identifier (login ou e-mail) e password.
Request
{
"identifier": "empresa.acme",
"password": "senha-do-portal"
}Response 200 OK
Retorna TokenOut - não usa o envelope padrão. Credenciais inválidas retornam 401 Unauthorized com detail: "Credenciais inválidas".
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"portal_user": {
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"perfil": "cliente",
"ativo": true,
"ultimo_login": "2026-07-03T09:12:00",
"data_hora_criacao": "2026-01-10T14:00:00"
},
"customer": {
"id": 1,
"razao_social": "ACME SERVICOS LTDA",
"nome_fantasia": "ACME",
"cpfecnpj": "12.345.678/0001-99"
}
}Proteção: público (não exige API Key) - rate limit portal_auth_token.
GET /portal/auth/me
Retorna o usuário do portal autenticado + o customer vinculado. O id_cliente é lido do token.
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - o id_cliente vem do token. O corpo da resposta é o PortalMeOut ({ portal_user, customer }).
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Usuário do portal recuperado",
"data": {
"portal_user": {
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"perfil": "cliente",
"ativo": true,
"ultimo_login": "2026-07-03T09:12:00",
"data_hora_criacao": "2026-01-10T14:00:00"
},
"customer": {
"id": 1,
"razao_social": "ACME SERVICOS LTDA",
"nome_fantasia": "ACME",
"cpfecnpj": "12.345.678/0001-99"
}
}
}Proteção: token do portal (get_current_portal_user).
POST /portal/users
Provisiona um novo acesso ao portal vinculado a um customer. Operação interna - restrita a perfil administrador ou diretoria.
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - os dados vão no corpo da requisição (PortalUserCreate): id_cliente, nome, login, email, password e, opcionalmente, perfil e ativo.
Request
{
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"password": "senha-inicial-forte",
"perfil": "cliente",
"ativo": true
}Response 201 Created
Login/e-mail duplicado retorna 409 Conflict.
{
"success": true,
"message": "Acesso ao portal criado com sucesso",
"data": {
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"perfil": "cliente",
"ativo": true,
"ultimo_login": null,
"data_hora_criacao": "2026-07-03T09:15:00"
}
}Proteção: perfil interno administrador/diretoria (require_perfil).
GET /portal/users
Lista a "lista de acessos" (usuários do portal) de uma empresa.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
id_cliente | int >= 1 | query | sim | customer.id cujos acessos serão listados. |
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Acessos do portal recuperados",
"data": [
{
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"perfil": "cliente",
"ativo": true,
"ultimo_login": "2026-07-03T09:12:00",
"data_hora_criacao": "2026-01-10T14:00:00"
}
]
}Proteção: perfil interno administrador/diretoria (require_perfil).
GET /portal/users/{portal_user_id}
Retorna um acesso específico (para edição no Hub). Inexistente retorna 404 Not Found.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
portal_user_id | int | path | sim | ID do acesso ao portal |
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Acesso do portal recuperado",
"data": {
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme",
"login": "empresa.acme",
"email": "financeiro@acme.com.br",
"perfil": "cliente",
"ativo": true,
"ultimo_login": "2026-07-03T09:12:00",
"data_hora_criacao": "2026-01-10T14:00:00"
}
}Proteção: perfil interno administrador/diretoria (require_perfil).
PUT /portal/users/{portal_user_id}
Edita nome/e-mail/perfil, ativa-desativa ou reseta a senha de um acesso. id_cliente e login são imutáveis de propósito.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
portal_user_id | int | path | sim | ID do acesso ao portal |
O corpo é um PortalUserUpdate com campos parciais: nome, email, perfil, ativo e password (reset).
Request (PortalUserUpdate - todos os campos opcionais)
{
"nome": "Financeiro Acme Matriz",
"email": "financeiro.matriz@acme.com.br",
"ativo": false
}Response 200 OK
{
"success": true,
"message": "Acesso ao portal atualizado com sucesso",
"data": {
"id": 3,
"id_cliente": 1,
"nome": "Financeiro Acme Matriz",
"login": "empresa.acme",
"email": "financeiro.matriz@acme.com.br",
"perfil": "cliente",
"ativo": false,
"ultimo_login": "2026-07-03T09:12:00",
"data_hora_criacao": "2026-01-10T14:00:00"
}
}Proteção: perfil interno administrador/diretoria (require_perfil).
DELETE /portal/users/{portal_user_id}
Remove um acesso ao portal.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
portal_user_id | int | path | sim | ID do acesso ao portal |
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Acesso ao portal excluído com sucesso",
"data": null
}Proteção: perfil interno administrador/diretoria (require_perfil).
Dados
Router portal_data_routes.py - lançamentos de lucro e sócios (QSA) da empresa do cliente logado. Toda rota filtra por current.id_cliente (do token).
GET /portal/lancamentos-lucro
Lista os lançamentos de lucro apenas da empresa do cliente autenticado. Sem registros, retorna error_response - { "success": false, "message": "Nenhum lançamento de lucro encontrado.", "details": [] }.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
competencia | str | query | não | Filtro por competência MM/YYYY. |
page | int >= 1 | query | não | Página. |
per_page | int 1..500 | query | não | Itens por página. |
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Lançamentos recuperados com sucesso!",
"data": [
{
"id": 12,
"id_cliente": 1,
"id_qsa_cliente": 10,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor_liquido": "1234.56",
"id_ata": null,
"valor_ata_utilizada": "0",
"natureza_rendimento": "Distribuição de lucros",
"status": "Em processamento",
"observacao": "Distribuição referente ao 4º trimestre.",
"id_usuario_created": null,
"id_portal_user_created": 7,
"data_hora_criacao": "2026-07-03T09:15:00"
}
]
}Proteção: token do portal (get_current_portal_user).
GET /portal/socios
Lista os sócios (quadro societário / QSA) apenas da empresa do cliente logado. Usado, por exemplo, pelo dropdown de sócios do formulário de lançamento. Sem sócios, retorna error_response com details: [] e a mensagem "Nenhum sócio encontrado.".
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - o id_cliente vem do token. Cada item da lista segue o schema PortalSocioOut.
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Sócios recuperados com sucesso!",
"data": [
{
"id": 10,
"nome": "João da Silva",
"cpfecnpj": "123.456.789-00",
"qualificacao": "Sócio-Administrador"
}
]
}Proteção: token do portal (get_current_portal_user).
POST /portal/lancamentos-lucro
Cria um lançamento de lucro para a empresa do cliente logado. O schema não aceita id_cliente (sempre o do token) nem status (fixado em "Em processamento"); a autoria fica em id_portal_user_created.
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - os dados vão no corpo da requisição (PortalLancamentoLucroCreate). Se id_qsa_cliente não pertencer à empresa retorna 404 Not Found; validação inválida retorna 422 Unprocessable Entity.
Request
{
"competencia": "01/2026",
"data_registro": "2026-01-15",
"valor_liquido": "1234.56",
"natureza_rendimento": "Distribuição de lucros",
"id_qsa_cliente": 10,
"data_pagamento": "2026-01-20",
"observacao": "Distribuição referente ao 4º trimestre."
}Response 201 Created
{
"success": true,
"message": "Lançamento de lucro criado com sucesso!",
"data": {
"id": 12,
"id_cliente": 1,
"id_qsa_cliente": 10,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor_liquido": "1234.56",
"id_ata": null,
"valor_ata_utilizada": "0",
"natureza_rendimento": "Distribuição de lucros",
"status": "Em processamento",
"observacao": "Distribuição referente ao 4º trimestre.",
"id_usuario_created": null,
"id_portal_user_created": 7,
"data_hora_criacao": "2026-07-03T09:15:00"
}
}Proteção: token do portal (get_current_portal_user).
PUT /portal/lancamentos-lucro/{lancamento_id}
Edita um lançamento de lucro da empresa do cliente logado (campos parciais). status e id_cliente nunca são aceitos; id_qsa_cliente pode ser reatribuído (validado contra a empresa).
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
lancamento_id | int | path | sim | ID do lançamento |
O corpo é um PortalLancamentoLucroUpdate (todos os campos opcionais). Regras: 404 se o lançamento não existe ou é de outra empresa (anti-IDOR); 409 Conflict se já saiu de "Em processamento"; 404 se o id_qsa_cliente informado não pertence à empresa.
Request (PortalLancamentoLucroUpdate - todos os campos opcionais)
{
"valor_liquido": "1500.00",
"observacao": "Valor ajustado após revisão."
}Response 200 OK
{
"success": true,
"message": "Lançamento de lucro atualizado!",
"data": {
"id": 12,
"id_cliente": 1,
"id_qsa_cliente": 10,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor_liquido": "1500.00",
"id_ata": null,
"valor_ata_utilizada": "0",
"natureza_rendimento": "Distribuição de lucros",
"status": "Em processamento",
"observacao": "Valor ajustado após revisão.",
"id_usuario_created": null,
"id_portal_user_created": 7,
"data_hora_criacao": "2026-07-03T09:15:00"
}
}Proteção: token do portal (get_current_portal_user).
Plano de Saúde
Router portal_plano_saude_routes.py - operadoras vinculadas (read-only), dependentes e lançamentos de plano de saúde da empresa do cliente logado.
GET /portal/operadoras-saude
Lista apenas as operadoras de saúde já vinculadas à empresa do cliente logado - derivadas dos vínculos em customer_plano_saude filtrados pelo id_cliente do token. Sem operadoras vinculadas, retorna error_response com details: [] e a mensagem "Nenhuma operadora vinculada a esta empresa.".
Operadoras são READ-ONLY no portal
O portal não cria nem vincula operadoras; apenas lista as que já foram vinculadas pela empresa no sistema interno. A base global de operadoras nunca é exposta (evita vazamento cross-tenant).
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - o id_cliente vem do token. Cada item da lista segue o schema PortalOperadoraOut.
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Operadoras recuperadas com sucesso!",
"data": [
{
"id": 1,
"razao_social": "Unimed Salvador",
"registro_ans": "123456",
"tipo_contratacao": "Coletivo empresarial",
"tipo_cobertura": "Ambulatorial + Hospitalar",
"abrangencia": "Nacional",
"status": "ativo"
}
]
}Proteção: token do portal (get_current_portal_user).
GET /portal/dependentes
Lista os dependentes apenas da empresa do cliente logado. Sem registros, retorna error_response com details: [] e a mensagem "Nenhum dependente encontrado.".
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - o id_cliente vem do token. Cada item da lista segue o schema PortalDependenteOut.
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Dependentes recuperados com sucesso!",
"data": [
{
"id": 1,
"nome_completo": "Maria da Silva",
"cpf": "123.456.789-00",
"data_nascimento": "1990-05-20",
"grau_parentesco": "Filho(a)",
"id_customer_qsa": 10,
"status": "ativo"
}
]
}Proteção: token do portal (get_current_portal_user).
POST /portal/dependentes
Cria um dependente para a empresa do cliente logado. id_cliente não aparece no body (fixado pelo token).
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - os dados vão no corpo da requisição (PortalDependenteCreate). Se id_customer_qsa for informado, o router valida que o sócio pertence à empresa (404 Not Found caso contrário).
Request
{
"nome_completo": "Maria da Silva",
"cpf": "123.456.789-00",
"data_nascimento": "1990-05-20",
"grau_parentesco": "Filho(a)",
"id_customer_qsa": 10
}Response 201 Created
{
"success": true,
"message": "Dependente criado com sucesso!",
"data": {
"id": 1,
"nome_completo": "Maria da Silva",
"cpf": "123.456.789-00",
"data_nascimento": "1990-05-20",
"grau_parentesco": "Filho(a)",
"id_customer_qsa": 10,
"status": "ativo"
}
}Proteção: token do portal (get_current_portal_user).
PUT /portal/dependentes/{dependente_id}
Edita um dependente da empresa do cliente logado (campos parciais). id_cliente nunca é aceito.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
dependente_id | int | path | sim | ID do dependente |
O corpo é um PortalDependenteUpdate (todos os campos opcionais). Regras: 404 se o dependente não existe ou é de outra empresa; 404 se id_customer_qsa informado não pertence à empresa.
Request (PortalDependenteUpdate - todos os campos opcionais)
{
"grau_parentesco": "Cônjuge",
"id_customer_qsa": 10
}Response 200 OK
{
"success": true,
"message": "Dependente atualizado!",
"data": {
"id": 1,
"nome_completo": "Maria da Silva",
"cpf": "123.456.789-00",
"data_nascimento": "1990-05-20",
"grau_parentesco": "Cônjuge",
"id_customer_qsa": 10,
"status": "ativo"
}
}Proteção: token do portal (get_current_portal_user).
GET /portal/lancamentos-plano-saude
Lista os lançamentos de plano de saúde apenas da empresa do cliente autenticado. Sem registros, retorna error_response com details: [] e a mensagem "Nenhum lançamento de plano de saúde encontrado.".
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
competencia | str | query | não | Filtro por competência MM/YYYY. |
page | int >= 1 | query | não | Página. |
per_page | int 1..500 | query | não | Itens por página. |
Request
Sem corpo de requisição.
Response 200 OK
{
"success": true,
"message": "Lançamentos recuperados com sucesso!",
"data": [
{
"id": 5,
"id_cliente": 1,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor": "1234.56",
"id_operadora_saude": 1,
"id_qsa_cliente": 10,
"id_dependente": 5,
"natureza_rendimento": "Reembolso plano de saúde",
"status": "pendente",
"observacao": "Mensalidade titular + 1 dependente."
}
]
}Proteção: token do portal (get_current_portal_user).
POST /portal/lancamentos-plano-saude
Cria um lançamento de plano de saúde para a empresa do cliente logado. O schema não aceita id_cliente (sempre o do token) nem status (fixado em "pendente"); a autoria fica em id_portal_user_created.
Parâmetros
Este endpoint não recebe parâmetros de rota ou query - os dados vão no corpo da requisição (PortalLancamentoPlanoSaudeCreate). Anti-IDOR: id_qsa_cliente, id_dependente (se houver) e id_operadora_saude são validados contra a empresa (404 se não pertencerem/estiverem vinculados); dependente que não pertence ao sócio titular informado retorna 422.
Request
{
"competencia": "01/2026",
"data_registro": "2026-01-15",
"valor": "1234.56",
"id_operadora_saude": 1,
"id_qsa_cliente": 10,
"id_dependente": 5,
"data_pagamento": "2026-01-20",
"natureza_rendimento": "Reembolso plano de saúde",
"observacao": "Mensalidade titular + 1 dependente."
}Response 201 Created
{
"success": true,
"message": "Lançamento de plano de saúde criado com sucesso!",
"data": {
"id": 5,
"id_cliente": 1,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor": "1234.56",
"id_operadora_saude": 1,
"id_qsa_cliente": 10,
"id_dependente": 5,
"natureza_rendimento": "Reembolso plano de saúde",
"status": "pendente",
"observacao": "Mensalidade titular + 1 dependente."
}
}Proteção: token do portal (get_current_portal_user).
PUT /portal/lancamentos-plano-saude/{lancamento_id}
Edita um lançamento de plano de saúde da empresa do cliente logado (campos parciais). status e id_cliente nunca são aceitos; revalida id_qsa_cliente, id_dependente e id_operadora_saude informados contra a empresa.
Parâmetros
| Parâmetro | Tipo | Local | Obrigatório | Descrição |
|---|---|---|---|---|
lancamento_id | int | path | sim | ID do lançamento |
O corpo é um PortalLancamentoPlanoSaudeUpdate (todos os campos opcionais). Regras: 404 se o lançamento não existe ou é de outra empresa (anti-IDOR); 409 Conflict se já saiu de "pendente" (pago/cancelado - travado).
Request (PortalLancamentoPlanoSaudeUpdate - todos os campos opcionais)
{
"valor": "1350.00",
"observacao": "Reajuste da mensalidade."
}Response 200 OK
{
"success": true,
"message": "Lançamento de plano de saúde atualizado!",
"data": {
"id": 5,
"id_cliente": 1,
"competencia": "01/2026",
"data_registro": "2026-01-15",
"data_pagamento": "2026-01-20",
"valor": "1350.00",
"id_operadora_saude": 1,
"id_qsa_cliente": 10,
"id_dependente": 5,
"natureza_rendimento": "Reembolso plano de saúde",
"status": "pendente",
"observacao": "Reajuste da mensalidade."
}
}Proteção: token do portal (get_current_portal_user).
Schemas
PortalLoginRequest
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
identifier | str | sim | Login ou e-mail do usuário do portal (3-120 caracteres). |
password | str | sim | Senha em texto puro (1-128), verificada contra o hash. |
PortalUserCreate
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
id_cliente | int | sim | customer.id ao qual o acesso pertence. |
nome | str | sim | Nome do acesso (1-200). |
login | str | sim | Login do usuário (3-100). Imutável após criação. |
email | EmailStr | sim | E-mail válido. |
password | str | sim | Senha em texto puro (8-128). |
perfil | str | não | Ex.: cliente, gestor. Default cliente. |
ativo | bool | não | Default true. |
PortalUserUpdate
Todos os campos são opcionais; id_cliente e login são imutáveis (não aparecem aqui).
| Campo | Tipo | Observações |
|---|---|---|
nome | str | Novo nome (1-200). |
email | EmailStr | Novo e-mail. |
perfil | str | Novo perfil (máx. 20). |
ativo | bool | Ativa/desativa o acesso. |
password | str | Nova senha (8-128), opcional (reset). |
PortalUserOut
Saída pública do acesso - nunca expõe senha_hash.
| Campo | Tipo | Notas |
|---|---|---|
id | int | ID do acesso ao portal. |
id_cliente | int | Empresa vinculada. |
nome | str | Nome do acesso. |
login | str | Login. |
email | str | E-mail (leitura tolerante). |
perfil | str | Perfil (cliente, gestor, ...). |
ativo | bool | Situação do acesso. |
ultimo_login | datetime | Último login (pode ser null). |
data_hora_criacao | datetime | Timestamp de criação. |
CustomerResumo
Dados mínimos do customer embutidos no login e no /me.
| Campo | Tipo | Notas |
|---|---|---|
id | int | ID do customer. |
razao_social | str | Razão social. |
nome_fantasia | str | Nome fantasia (pode ser null). |
cpfecnpj | str | CNPJ/CPF (pode ser null). |
PortalSocioOut
| Campo | Tipo | Notas |
|---|---|---|
id | int | customer_qsa.id. |
nome | str | Nome do sócio. |
cpfecnpj | str | CPF/CNPJ do sócio (pode ser null). |
qualificacao | str | Ex.: Sócio-Administrador (pode ser null). |
PortalLancamentoLucroCreate
Não aceita id_cliente nem status (fixados pelo backend).
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
competencia | str | sim | Formato MM/YYYY (7 caracteres). |
data_registro | date | sim | Data do registro. |
valor_liquido | Decimal | sim | Valor >= 0 (validador canônico). |
natureza_rendimento | str | sim | Natureza do rendimento (1-100). |
id_qsa_cliente | int | não | Sócio titular (validado contra a empresa). |
data_pagamento | date | não | Data de pagamento. |
observacao | str | não | Observação livre. |
PortalLancamentoLucroUpdate
Todos os campos opcionais (patch). status e id_cliente nunca são aceitos.
| Campo | Tipo | Observações |
|---|---|---|
competencia | str | MM/YYYY. |
data_registro | date | - |
data_pagamento | date | - |
valor_liquido | Decimal | Valor >= 0. |
natureza_rendimento | str | 1-100. |
observacao | str | - |
id_qsa_cliente | int | Reatribui o sócio titular. |
PortalOperadoraOut
Read-only - apenas operadoras já vinculadas à empresa.
| Campo | Tipo | Notas |
|---|---|---|
id | int | ID da operadora. |
razao_social | str | Ex.: Unimed Salvador. |
registro_ans | str | Registro ANS (pode ser null). |
tipo_contratacao | str | Ex.: Coletivo empresarial (pode ser null). |
tipo_cobertura | str | Ex.: Ambulatorial + Hospitalar (null ok). |
abrangencia | str | Ex.: Nacional (pode ser null). |
status | str | Situação da operadora (ex.: ativo). |
PortalDependenteCreate
id_cliente não aparece (fixado pelo token).
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
nome_completo | str | sim | Nome do dependente (1-100). |
cpf | str | não | CPF (máx. 14). |
data_nascimento | date | não | Data de nascimento. |
grau_parentesco | str | não | Ex.: Filho(a) (máx. 50). |
id_customer_qsa | int | não | Sócio titular (validado contra a empresa). |
PortalDependenteUpdate
Todos os campos opcionais (patch). id_cliente nunca é aceito. Mesmos campos de PortalDependenteCreate: nome_completo, cpf, data_nascimento, grau_parentesco, id_customer_qsa.
PortalDependenteOut
| Campo | Tipo | Notas |
|---|---|---|
id | int | ID do dependente. |
nome_completo | str | Nome completo. |
cpf | str | CPF (pode ser null). |
data_nascimento | date | Data de nascimento (pode ser null). |
grau_parentesco | str | Grau de parentesco (pode ser null). |
id_customer_qsa | int | Sócio titular vinculado (pode ser null). |
status | str | Situação (ex.: ativo). |
PortalLancamentoPlanoSaudeCreate
Não aceita id_cliente nem status (fixados pelo backend).
| Campo | Tipo | Obrigatório | Observações |
|---|---|---|---|
competencia | str | sim | Formato MM/YYYY. |
data_registro | date | sim | Data do registro. |
valor | Decimal | sim | Valor (validador canônico do domínio). |
id_operadora_saude | int | sim | Operadora vinculada à empresa (validada). |
id_qsa_cliente | int | sim | Sócio titular (validado contra a empresa). |
id_dependente | int | não | Dependente coberto (validado contra a empresa). |
data_pagamento | date | não | Data de pagamento. |
natureza_rendimento | str | não | Máx. 100. |
observacao | str | não | Máx. 2000. |
PortalLancamentoPlanoSaudeUpdate
Todos os campos opcionais (patch). status e id_cliente nunca são aceitos. Campos: competencia, data_registro, data_pagamento, valor, id_operadora_saude, id_qsa_cliente, id_dependente, natureza_rendimento, observacao.
PortalLancamentoPlanoSaudeOut
| Campo | Tipo | Notas |
|---|---|---|
id | int | ID do lançamento. |
id_cliente | int | Empresa (do token). |
competencia | str | MM/YYYY. |
data_registro | date | Data do registro. |
data_pagamento | date | Pode ser null. |
valor | Decimal | Valor do lançamento. |
id_operadora_saude | int | Operadora vinculada. |
id_qsa_cliente | int | Sócio titular. |
id_dependente | int | Dependente (pode ser null). |
natureza_rendimento | str | Pode ser null. |
status | str | Ciclo de vida (pendente, ...). |
observacao | str | Pode ser null. |
Notas
id_cliente vem SEMPRE do token
O cliente nunca informa id_cliente/customer_id no path, query ou body das rotas de dados. Ele é lido do token (get_current_portal_user). Qualquer valor de id_cliente ou status enviado no corpo é ignorado pelo backend (anti mass-assignment). Acessar um recurso de outra empresa retorna 404 - o portal não revela sequer a existência de dados de outro tenant.
Lançamentos travam após processamento
Um lançamento só pode ser editado pelo portal enquanto está no status inicial: "Em processamento" (lucro) ou "pendente" (plano de saúde). Depois que a contabilidade processa/cancela o lançamento no sistema interno, a edição via portal retorna 409 Conflict.
Listagens vazias usam error_response
Diferentemente de alguns endpoints internos que devolvem data: [], as listagens do portal retornam o envelope de erro { success: false, message, details: [] } quando não há registros. Trate ambos os formatos no front.
- Toda resposta (exceto o login
POST /auth/token, que devolveTokenOutcru) segue o envelope{ success, message, data }. - As rotas de
/portal/userssão administrativas (Hub interno, perfiladministrador/diretoriaviarequire_perfil) e não fazem parte do fluxo do cliente final - elas apenas provisionam os acessos que depois autenticam no portal.