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/portal

A 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 em POST /portal/auth/token (JSON com identifier + password) e recebe um JWT que carrega o id_cliente da empresa. Esse token protege GET /portal/auth/me e todas as rotas de dados (lucro, sócios, plano de saúde, dependentes, operadoras). Não há escopos recurso:acao; a autorização é o próprio vínculo id_cliente do token. POST /auth/token é público (sem token), porém protegido pelo mesmo rate limit do /auth/token interno.

  • Perfil interno - require_perfil("administrador", "diretoria"). As rotas de gestão de acessos (/portal/users) são operadas pelo Hub interno e exigem um Principal interno com perfil RBAC administrador ou diretoria - 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âmetroTipoLocalObrigatórioDescrição
id_clienteint >= 1querysimcustomer.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âmetroTipoLocalObrigatórioDescrição
portal_user_idintpathsimID 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âmetroTipoLocalObrigatórioDescrição
portal_user_idintpathsimID 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âmetroTipoLocalObrigatórioDescrição
portal_user_idintpathsimID 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âmetroTipoLocalObrigatórioDescrição
competenciastrquerynãoFiltro por competência MM/YYYY.
pageint >= 1querynãoPágina.
per_pageint 1..500querynãoItens 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âmetroTipoLocalObrigatórioDescrição
lancamento_idintpathsimID 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âmetroTipoLocalObrigatórioDescrição
dependente_idintpathsimID 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âmetroTipoLocalObrigatórioDescrição
competenciastrquerynãoFiltro por competência MM/YYYY.
pageint >= 1querynãoPágina.
per_pageint 1..500querynãoItens 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âmetroTipoLocalObrigatórioDescrição
lancamento_idintpathsimID 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

CampoTipoObrigatórioObservações
identifierstrsimLogin ou e-mail do usuário do portal (3-120 caracteres).
passwordstrsimSenha em texto puro (1-128), verificada contra o hash.

PortalUserCreate

CampoTipoObrigatórioObservações
id_clienteintsimcustomer.id ao qual o acesso pertence.
nomestrsimNome do acesso (1-200).
loginstrsimLogin do usuário (3-100). Imutável após criação.
emailEmailStrsimE-mail válido.
passwordstrsimSenha em texto puro (8-128).
perfilstrnãoEx.: cliente, gestor. Default cliente.
ativoboolnãoDefault true.

PortalUserUpdate

Todos os campos são opcionais; id_cliente e login são imutáveis (não aparecem aqui).

CampoTipoObservações
nomestrNovo nome (1-200).
emailEmailStrNovo e-mail.
perfilstrNovo perfil (máx. 20).
ativoboolAtiva/desativa o acesso.
passwordstrNova senha (8-128), opcional (reset).

PortalUserOut

Saída pública do acesso - nunca expõe senha_hash.

CampoTipoNotas
idintID do acesso ao portal.
id_clienteintEmpresa vinculada.
nomestrNome do acesso.
loginstrLogin.
emailstrE-mail (leitura tolerante).
perfilstrPerfil (cliente, gestor, ...).
ativoboolSituação do acesso.
ultimo_logindatetimeÚltimo login (pode ser null).
data_hora_criacaodatetimeTimestamp de criação.

CustomerResumo

Dados mínimos do customer embutidos no login e no /me.

CampoTipoNotas
idintID do customer.
razao_socialstrRazão social.
nome_fantasiastrNome fantasia (pode ser null).
cpfecnpjstrCNPJ/CPF (pode ser null).

PortalSocioOut

CampoTipoNotas
idintcustomer_qsa.id.
nomestrNome do sócio.
cpfecnpjstrCPF/CNPJ do sócio (pode ser null).
qualificacaostrEx.: Sócio-Administrador (pode ser null).

PortalLancamentoLucroCreate

Não aceita id_cliente nem status (fixados pelo backend).

CampoTipoObrigatórioObservações
competenciastrsimFormato MM/YYYY (7 caracteres).
data_registrodatesimData do registro.
valor_liquidoDecimalsimValor >= 0 (validador canônico).
natureza_rendimentostrsimNatureza do rendimento (1-100).
id_qsa_clienteintnãoSócio titular (validado contra a empresa).
data_pagamentodatenãoData de pagamento.
observacaostrnãoObservação livre.

PortalLancamentoLucroUpdate

Todos os campos opcionais (patch). status e id_cliente nunca são aceitos.

CampoTipoObservações
competenciastrMM/YYYY.
data_registrodate-
data_pagamentodate-
valor_liquidoDecimalValor >= 0.
natureza_rendimentostr1-100.
observacaostr-
id_qsa_clienteintReatribui o sócio titular.

PortalOperadoraOut

Read-only - apenas operadoras já vinculadas à empresa.

CampoTipoNotas
idintID da operadora.
razao_socialstrEx.: Unimed Salvador.
registro_ansstrRegistro ANS (pode ser null).
tipo_contratacaostrEx.: Coletivo empresarial (pode ser null).
tipo_coberturastrEx.: Ambulatorial + Hospitalar (null ok).
abrangenciastrEx.: Nacional (pode ser null).
statusstrSituação da operadora (ex.: ativo).

PortalDependenteCreate

id_cliente não aparece (fixado pelo token).

CampoTipoObrigatórioObservações
nome_completostrsimNome do dependente (1-100).
cpfstrnãoCPF (máx. 14).
data_nascimentodatenãoData de nascimento.
grau_parentescostrnãoEx.: Filho(a) (máx. 50).
id_customer_qsaintnãoSó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

CampoTipoNotas
idintID do dependente.
nome_completostrNome completo.
cpfstrCPF (pode ser null).
data_nascimentodateData de nascimento (pode ser null).
grau_parentescostrGrau de parentesco (pode ser null).
id_customer_qsaintSócio titular vinculado (pode ser null).
statusstrSituação (ex.: ativo).

PortalLancamentoPlanoSaudeCreate

Não aceita id_cliente nem status (fixados pelo backend).

CampoTipoObrigatórioObservações
competenciastrsimFormato MM/YYYY.
data_registrodatesimData do registro.
valorDecimalsimValor (validador canônico do domínio).
id_operadora_saudeintsimOperadora vinculada à empresa (validada).
id_qsa_clienteintsimSócio titular (validado contra a empresa).
id_dependenteintnãoDependente coberto (validado contra a empresa).
data_pagamentodatenãoData de pagamento.
natureza_rendimentostrnãoMáx. 100.
observacaostrnãoMá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

CampoTipoNotas
idintID do lançamento.
id_clienteintEmpresa (do token).
competenciastrMM/YYYY.
data_registrodateData do registro.
data_pagamentodatePode ser null.
valorDecimalValor do lançamento.
id_operadora_saudeintOperadora vinculada.
id_qsa_clienteintSócio titular.
id_dependenteintDependente (pode ser null).
natureza_rendimentostrPode ser null.
statusstrCiclo de vida (pendente, ...).
observacaostrPode 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 devolve TokenOut cru) segue o envelope { success, message, data }.
  • As rotas de /portal/users são administrativas (Hub interno, perfil administrador/diretoria via require_perfil) e não fazem parte do fluxo do cliente final - elas apenas provisionam os acessos que depois autenticam no portal.