Documentação da API Lastreo
Versão: 1.63 Data da Última Atualização: 30/05/2026
1. Introdução
O que é o Lastreo?
Lastreo é uma plataforma de assistência clínica projetada para conectar pacientes e equipes de saúde através de múltiplos canais: chat integrado no aplicativo (in-app), WhatsApp e Telegram. Sua missão é facilitar o acompanhamento contínuo, o registro de dados de saúde em tempo real e a comunicação eficiente, com um módulo especializado em Esclerose Múltipla.
Para Quem é Este Documento?
Esta documentação é um guia técnico para desenvolvedores que precisam integrar aplicativos com a API do Lastreo. Contém os endpoints, fluxos de requisição e modelos de dados necessários para a integração.
Changelog
| Versão | Data | Alterações |
|---|---|---|
| 1.74 | 29/06/2026 | client_user_ref agora garante idempotência de re-tentativas. Ao criar uma sessão de verificação hospedada (POST /api/v1/client/verification-sessions), passar um client_user_ref estável (o ID da pessoa no seu sistema) faz com que re-tentativas da mesma pessoa reusem a mesma identidade em vez de criar um cadastro novo — e não há nova cobrança por re-verificar a mesma pessoa. Recomendado para fluxos em que o usuário pode recomeçar a verificação (após erro, abandono ou múltiplas tentativas). Sem client_user_ref, cada sessão é tratada como uma pessoa distinta (comportamento inalterado). Vale para kind: "physician" e kind: "student". Zero impacto no consumidor App EM. |
| 1.73 | 21/06/2026 | Verificação bronze inclui declaração de identidade. No fluxo hospedado, a verificação rápida (bronze) passa a exigir que o médico aceite uma declaração, sob as penas da lei, de que é o titular do CRM informado — camada de responsabilização (o registro do aceite, com data/hora e origem, fica selado para auditoria). É uma etapa dentro da página hospedada; sua integração não muda (mesma criação de sessão e mesmos webhooks). Continua valendo: bronze confirma que o registro profissional existe e está regular; para vínculo de identidade por biometria use prata, e por assinatura digital use ouro. Zero impacto no consumidor App EM. |
| 1.72 | 21/06/2026 | Verificação é produzida apenas pelo fluxo hospedado. A PRODUÇÃO de qualquer tier passa a acontecer só na superfície hospedada do Lastreo (onde o próprio médico confirma a identidade). POST /client/doctors/{id}/verify-tier passa a responder needs_doctor_action para bronze/prata/ouro ainda não verificados (registra a pendência; a cobrança ocorre na conclusão hospedada) — antes o bronze retornava produced na hora. POST /client/doctors apenas registra/vincula o médico (não produz bronze). O reconhecimento cross-tenant (médico já verificado na rede) continua imediato e gratuito por API — o efeito de rede é preservado. Para verificar um médico ainda não verificado, crie uma sessão (POST /client/verification-sessions) e direcione-o ao embed_url. Ação necessária: se você tratava outcome: "produced" no verify-tier para bronze, passe a tratar needs_doctor_action + fluxo hospedado. Zero impacto no consumidor App EM. |
| 1.71 | 21/06/2026 | Verificação hospedada agora cobre os três tiers. A página/iframe hospedado (POST /api/v1/client/verification-sessions) passa a apresentar o método de acordo com o required_tier da sessão: bronze (confirmação rápida só com CRM + UF + nome, sem documento), prata (carteira + selfie, já existente) ou ouro (assinatura digital ICP-Brasil — o médico baixa um termo, assina com o certificado AR-CFM e reenvia, tudo dentro do iframe). Antes o iframe só fazia prata. O Lastreo Link (reuso de identidade já verificada) continua sempre oferecido. Sem required_tier, o padrão permanece prata (zero impacto em integrações existentes). Os webhooks verification.approved carregam o tier final (bronze/prata/ouro) como antes. Zero impacto no consumidor App EM. |
| 1.70 | 20/06/2026 | Verificação bronze mais precisa. A aprovação automática (bronze) passa a ser sempre ancorada na confirmação do registro profissional oficial (situação regular + nome conferido), e o casamento de nome ficou mais tolerante a variações legítimas (nome do meio omitido, ordem dos sobrenomes) — menos médicos legítimos reprovados, sem afrouxar a checagem. Sem mudança no contrato da API (mesmos endpoints, schemas e tiers). Zero impacto no consumidor App EM. |
| 1.69 | 19/06/2026 | Cobertura bronze ampliada. A verificação bronze passa a confirmar o médico também pelo registro profissional oficial (situação regular + nome conferido), ampliando a cobertura para quem não tem atividade assistencial ativa registrada (ex.: atuação exclusivamente privada) — antes esses casos exigiam prata/ouro. Sem mudança no contrato da API (mesmos endpoints, schemas e tiers); apenas mais médicos passam a ser elegíveis a bronze automático. Zero impacto no consumidor App EM. |
| 1.68 | 18/06/2026 | Piso de tier na verificação hospedada (required_tier). POST /api/v1/client/verification-sessions passa a aceitar required_tier (bronze | prata | ouro; aliases basic/silver/strong/gold) — o tier mínimo que a sessão aceita. Com piso definido: o Lastreo Link só conclui se a identidade reusada tiver tier ≥ piso (abaixo disso, o médico é direcionado à verificação completa); e a verificação feita dentro do fluxo hospedado (carteira + selfie = prata) não satisfaz piso ouro — ouro depende de identidade já-ouro via Lastreo Link (a assinatura ICP-Brasil é fora do fluxo hospedado). O campo vale só para kind: "physician" e é devolvido em GET /client/verification-sessions/{id}. Sem required_tier, o comportamento é inalterado. Zero impacto no consumidor App EM. |
| 1.67 | 18/06/2026 | Lastreo Link — reconhecimento exclusivamente por e-mail. O endpoint do fluxo hospedado POST /embed/v/{token}/recognize agora aceita apenas email no corpo; o campo cpf (antes opcional) foi descontinuado. O reconhecimento entre clientes usa o e-mail verificado como chave e envia o código de confirmação para esse mesmo e-mail — chave e canal de prova de posse coincidem. Sem impacto para quem usa a página hospedada (ela sempre pediu o e-mail); se você chamava o endpoint diretamente enviando cpf, passe a enviar email. Zero impacto no consumidor App EM. |
| 1.66 | 12/06/2026 | GET /api/v1/client/verification-sessions/{id} agora preenche final_tier para estudantes. Antes vinha null em sessões kind=student (o tier só aparecia no webhook ou em /client/students). Agora final_tier reflete o tier final da sessão independentemente do tipo — bronze/prata/ouro para médico, verified/verified_strong para estudante — e a resposta passa a incluir kind (student | physician). Você lê o resultado direto do GET, sem lookup adicional. Aditivo (médico inalterado; estudante deixa de vir null). |
| 1.65 | 12/06/2026 | Verificação de estudante — método documento mais flexível. O comprovante de matrícula agora aceita PDF (além de imagem). Selfie e documento com foto passam a ser opcionais: o tier verified é concedido a partir do comprovante (curso de Medicina + instituição reconhecida + nome conferido); enviar selfie + documento com foto eleva o resultado para verified_strong (identidade vinculada por biometria). Tudo acontece dentro da verificação hospedada — sem mudança na sua integração; apenas garanta que seu código trate verified_strong como estudante verificado (assim como verified). |
| 1.64 | 09/06/2026 | Verificação de estudante de medicina (identidade que evolui para médico). A verificação hospedada (POST /api/v1/client/verification-sessions) agora aceita kind: "student" (default "physician"). Numa sessão de estudante, a página hospedada oferece dois caminhos — e-mail institucional (código) ou comprovante de matrícula — e confirma que a pessoa é estudante de Medicina em instituição reconhecida. O tier resultante é verified (ou verified_strong com identidade jurídica forte). Liste os estudantes do seu tenant via GET /api/v1/client/students. Os webhooks verification.approved/verification.rejected passam a disparar também para estudantes, com kind: "student" no data. Ciclo de vida: quando o estudante obtém o CRM e se verifica como médico, a MESMA identidade Lastreo migra para médico (sai de /client/students, aparece em /client/doctors com tier bronze/prata/ouro). Guia completo no Fluxo 18.6. Zero impacto no consumidor App EM e no fluxo de médico (sessões sem kind continuam sendo de médico). |
| 1.63 | 28/05/2026 | Valores de tier de verificação agora são bronze / prata / ouro. Os campos que retornam o tier — final_tier (sessões de verificação hospedada), tier (webhooks verification.submitted/approved/rejected), verification_tier (médicos em GET /api/v1/client/doctors e no snapshot de compliance) — passam a emitir prata (antes basic) e ouro (antes strong). bronze é inalterado. As chaves de by_tier no snapshot de compliance passam de basic/strong para prata/ouro. Os nomes antigos continuam aceitos na ENTRADA (POST /client/doctors/{id}/verify-tier, override de admin): basic/silver → prata, strong/gold → ouro. Ação necessária: se você compara o tier como string no seu lado (ex.: if (tier === "basic")), atualize para os novos valores prata/ouro. |
| 1.62 | 28/05/2026 | Endpoints de integração /client/* aceitam X-API-KEY (server-to-server). Antes só Bearer JWT de client_admin (dashboard). Agora os endpoints de integração — POST/GET /api/v1/client/verification-sessions, POST/GET /api/v1/client/doctors, POST /api/v1/client/doctors/{id}/verify-tier, GET /api/v1/client/verifications/usage, GET /api/v1/client/usage, GET /api/v1/client/usage/by-patient, GET /api/v1/client/compliance/snapshot — aceitam X-API-KEY: lk_... (a forma recomendada para o backend do seu sistema chamar o Lastreo: revogável, escopada ao tenant, sem credencial de login no servidor) ou Bearer JWT (dashboard). Crie a API key no dashboard em Developer → API Keys (mostrada uma vez). Endpoints de gerenciamento de conta (api-keys, webhooks, integrações, push-configs, devices) seguem exclusivos do dashboard via JWT — não se gerencia uma API key usando uma API key. Zero impacto no consumidor App EM. |
| 1.61 | 28/05/2026 | Correlação de webhooks de verificação por sessão (estilo client_reference_id do Stripe). Quando você cria uma sessão de verificação hospedada (POST /api/v1/client/verification-sessions), pode passar um client_user_ref — o identificador do médico no seu próprio sistema. A partir de agora, os webhooks de verificação originados daquela sessão carregam no data os campos session_id e client_user_ref, permitindo correlacionar o webhook ao seu usuário sem nenhum lookup reverso — independentemente da ordem em que o webhook e a resposta de criação chegam. session_id é exatamente o mesmo campo session_id (string pública vs_…) que o POST /client/verification-sessions devolveu no corpo da resposta — NÃO o id numérico (PK interna). Eventos afetados: verification.submitted, verification.approved, verification.rejected. Isolamento multi-tenant garantido: como o médico é uma entidade global (o mesmo CRM pode existir em vários clientes Lastreo), o evento de verificação é entregue a todos os clientes que conhecem aquele médico — mas session_id/client_user_ref são incluídos somente no webhook do cliente dono da sessão. Os demais clientes recebem o mesmo evento sem esses campos. Eventos que não vêm de uma sessão hospedada (ex.: cadastro bronze via POST /api/v1/client/doctors, verificação ICP-Brasil feita diretamente pelo médico) não trazem esses campos — continue usando o user_id do médico. Exemplo de data recebido pelo cliente dono: {"user_id": 42, "tier": "prata", "verified_crm": "123456", "verified_uf": "SE", "session_id": "vs_ab12cd34", "client_user_ref": "seu-sistema:medico:789"}. Nenhuma ação necessária se você não usa sessões hospedadas (campos só aparecem quando há client_user_ref na sessão). |
| 1.60 | 28/05/2026 | Billing de verificação por tier. Endpoints novos para client_admin: GET /api/v1/client/verifications/usage?days=N (contagem de verificações por tier no período + total, quota mensal do plano, overage e estimativa de billing mensal em BRL) e POST /api/v1/client/doctors/{doctor_id}/verify-tier com body {"tier": "bronze"\|"prata"\|"ouro"} (aliases legacy basic/strong/silver/gold também aceitos). Regra de cobrança: você paga pela verificação de um tier quando ela é produzida pela primeira vez para aquele médico; se ele já tem aquele tier (ou superior) porque já foi verificado antes na rede Lastreo, o reconhecimento é gratuito. Subir de tier conta como verificação nova. O verify-tier responde recognized (grátis), produz o tier na hora (bronze), ou needs_doctor_action para prata/ouro (o médico completa pelo fluxo próprio e a cobrança ocorre na aprovação). Preços vigentes em /docs-public/pricing.html. Zero impacto no consumidor App EM (endpoints do dashboard B2B). |
| 1.59.1 | 27/05/2026 | BYOK (chave de IA própria) passa a ser obrigatório. Todo tenant precisa configurar uma integração de IA em /dashboard/integrations/ (aba IA / Modelo) antes de usar o chat. Sem BYOK: o chat retorna um erro estruturado pedindo a configuração; POST /chat/send com upload de mídia retorna HTTP 422; e o resumo automático de conversa é silenciosamente omitido (o chat de texto segue funcionando). Para upload de mídia e resumo de conversa, a chave precisa ser de um provider compatível com a Files API do Gemini (openai_compatible apontando para o endpoint do Gemini, ou google_native); outros providers atendem o chat de texto mas não mídia/resumo. Zero impacto para quem já tem BYOK configurado. |
| 1.59 | 27/05/2026 | Lastreo Link — reuso da identidade verificada entre clientes. Um médico já verificado em qualquer cliente Lastreo pode confirmar a identidade em um cliente novo via código enviado por email e herda o tier na hora, sem reenviar documentos (opt-in explícito do médico, LGPD Art. 7). Endpoints do fluxo hospedado: POST /embed/v/{token}/recognize (informa o e-mail cadastrado, dispara o código), POST /embed/v/{token}/recognize/confirm (valida o código e herda a identidade — o webhook de aprovação carrega via_lastreo_link: true), POST /embed/v/{token}/link-consent (opt-in/opt-out do reuso). Efeito de rede: cada cliente novo amplia a base de médicos reconhecíveis pelos próximos. Zero impacto no consumidor App EM. |
| 1.58 | 26/05/2026 | Verificação hospedada (modal embed estilo Stripe Elements). Crie uma sessão via POST /api/v1/client/verification-sessions (recebe embed_url) e consulte o resultado via GET /api/v1/client/verification-sessions/{id}. A página hospedada é um wizard de 4 passos (identificação → carteira → selfie → resultado) com tema customizável (theme_primary_color), branding opcional (hide_branding, para white-label) e redirect ao concluir (completion_redirect_url). SDK JS drop-in em /dashboard/sdk/lastreo.js (Lastreo({embedUrl}).mount(...) ou .openModal(...)). A foto da carteira e a selfie vão direto para o Lastreo — nunca passam pelo seu backend. Sob LGPD, você deixa de ser controlador do dado biométrico. Zero impacto no consumidor App EM. |
| 1.57 | 26/05/2026 | Verificação prata com decisão automática. A submissão de tier prata (POST /doctors/me/verification/basic/submit) passa por uma avaliação automática que pode aprovar, rejeitar ou encaminhar para revisão humana — sem que documento ou biometria saiam da infraestrutura Lastreo (zero data egress). Os webhooks verification.approved / verification.rejected passam a carregar auto_approved / auto_rejected e um indicador de confiança da decisão. Nenhum subprocessador novo; nenhum dado sai do servidor Lastreo. Zero impacto no consumidor App EM. |
| 1.56 | 26/05/2026 | Compliance snapshot. Novo endpoint GET /api/v1/client/compliance/snapshot retorna uma prova-de-tratamento estruturada (médicos por tier, registros oficiais consultados com data da última atualização, atividade de auditoria do período, DSRs e incidentes, atestações LGPD) — um JSON que você pode anexar a contratos comerciais ou apresentar em inquérito da ANPD. Acompanha reorganização do dashboard B2B em torno de verificação + compliance. Zero impacto no consumidor App EM. |
| 1.55 | 26/05/2026 | Refinamento interno da verificação bronze (melhor detecção de registros profissionais com status irregular). Sem mudança de contrato da API. Zero impacto no consumidor. |
| 1.54 | 26/05/2026 | Cadastro de médico pelo cliente B2B com verificação automática. POST /api/v1/client/doctors (role client_admin): envie {full_name, crm, uf, specialty?, email?} e o médico é criado já com tentativa de verificação automática. Se elegível, retorna verification_tier=bronze + status=approved em instantes; caso contrário fica pendente para prata/ouro. A resposta inclui bronze_attempted, bronze_eligible e bronze_reason para você decidir o fluxo. Cadastros repetidos do mesmo médico (por email ou por CRM/UF) atualizam em vez de duplicar. Zero impacto no consumidor existente (endpoint novo). |
| 1.53 | 26/05/2026 | Tier bronze — verificação automática sem documento físico. Novo tier bronze (abaixo de basic/strong). Endpoint POST /api/v1/doctors/me/verification/bronze (rate-limited): cruza o CRM/UF declarados com registros oficiais e aprova na hora quando há correspondência sem pendências, derivando a especialidade automaticamente. Casos que não passam seguem para os fluxos prata/ouro. O webhook verification.approved pode agora trazer tier=bronze. Zero impacto no consumidor existente (tier novo; basic/strong inalterados). |
| 1.52 | 25/05/2026 | Ferramentas internas de apoio à revisão de verificações prata (uso administrativo Lastreo). Endpoints internos apenas para admin; zero impacto no consumidor App EM. |
| 1.51 | 24/05/2026 | Configuração de push (FCM) por evento. Endpoints /api/v1/client/*: GET /push-configs (catálogo de eventos alert.created + medication_reminder.due com a config do tenant), PUT /push-configs/{event_type} (enabled + custom_title/custom_body opcionais), DELETE /push-configs/{event_type} (volta ao default), GET /devices (device tokens FCM dos usuários do tenant), POST /push-test (push de teste para um device). Eventos de médico (verification.approved/rejected) não são configuráveis (médico é global; push sempre dispara). Gerenciável em /dashboard/events/. Zero impacto no consumidor App EM (default enabled=true preserva o comportamento atual). |
| 1.50 | 24/05/2026 | Push nativo (FCM) em aprovação/rejeição de verificação. Quando a verificação de um médico é aprovada ou rejeitada, o Lastreo dispara push para o device token registrado (POST /api/v1/users/me/device-tokens), com data {type:"verification", approved, tier, doctor_user_id} para deep-link. O webhook verification.rejected passou a ser emitido também (antes só o approved). Zero impacto no consumidor App EM (sem device token, vira no-op). |
| 1.49 | 24/05/2026 | Mudança interna de configuração de autenticação serviço-a-serviço. SERVICE_API_KEY continua válida indefinidamente. Zero impacto no consumidor App EM. |
| 1.48 | 24/05/2026 | Atribuição de uso de IA por tenant e por paciente. GET /api/v1/client/usage passa a refletir corretamente o consumo do tenant (inclui chamadas de médico atendendo paciente do tenant). Endpoint novo GET /api/v1/client/usage/by-patient?days=N&limit=M retorna ranking de pacientes por consumo de IA + bloco unattributed para chamadas sem paciente-alvo. Zero impacto no consumidor App EM (analytics). |
| 1.47 | 20/05/2026 | Painel de integrações BYOK + visibilidade de médicos. /dashboard/integrations/ com abas IA / WhatsApp / Telegram (forms não-destrutivos — o segredo só é atualizado se preenchido). Página /dashboard/doctors/ read-only: a verificação de médico é centralizada no Lastreo (1 CRM = 1 médico, entidade global); o cliente recebe o resultado por webhook, mas não aprova/rejeita. Endpoint GET /api/v1/client/doctors lista os médicos verificados que atendem pacientes do tenant (com verification_status, verification_tier, verified_crm, verified_uf, verified_specialty, patient_count_in_tenant). Zero impacto no consumidor App EM. |
| 1.46 | 20/05/2026 | Roteamento de webhook de mensageria por tenant (BYOK). O webhook do WhatsApp passa a identificar o tenant pelo phone_number_id do payload e validar a assinatura com o segredo do próprio tenant. Endpoint novo /webhooks/telegram/{path_token} por tenant. O envio de mensagens usa as credenciais do tenant. Suporte a mais um provider de IA. Zero impacto no App EM (sem integração própria, segue no padrão). |
| 1.45 | 20/05/2026 | BYOK — Bring Your Own Key (Fase 1). Cliente pluga a própria chave de IA (OpenAI / Mistral / modelo local compatível com OpenAI / Gemini) e o chat passa a rodar nela. Endpoints /api/v1/client/integrations/{ai,whatsapp,telegram} (GET/PUT/DELETE), com os segredos criptografados at rest. Zero impacto no consumidor App EM. |
| 1.44 | 20/05/2026 | Páginas públicas + request access. Páginas em /docs-public/: pricing, legal (ToS + DPA + AUP + Privacy), request-access. Endpoint público POST /api/v1/public/access-request (rate-limited, sem auth, com anti-spam). Endpoints admin para revisar pedidos. Zero impacto no consumidor App EM. |
| 1.43 (frontend) | 20/05/2026 | Dashboard self-service B2B (frontend). https://lastreo.com/dashboard/ — login + páginas de overview, API keys, webhooks, uso, erros e configurações. Zero impacto no consumidor App EM. |
| 1.43 | 20/05/2026 | Dashboard self-service B2B (backend). Role client_admin + endpoints /api/v1/client/* para o cliente gerenciar o próprio tenant: /me, /api-keys (CRUD + revoke), /webhooks (CRUD + deliveries + retry + test), /usage, /errors. Zero impacto no consumidor App EM. |
| 1.42 | 20/05/2026 | Ambiente sandbox. https://lastreo.com/sandbox/ — isolado de produção, com stubs para IA/FCM/email/mensageria (não enviam de verdade). Troque a base URL e teste fluxos completos sem afetar produção nem consumir cota de IA. Zero impacto no consumidor App EM (produção segue em /api/v1/...). |
| 1.41 | 19/05/2026 | Status page + changelog público. /docs-public/status.html e /docs-public/changelog.html. Endpoint público GET /status agrega estado operacional + estatísticas de webhook 24h + incidentes recentes (sem expor detalhes sensíveis). Zero impacto no consumidor. |
| 1.40 | 19/05/2026 | Webhooks B2B. Cada cliente configura webhooks (URL + segredo + lista de eventos). Assinatura HMAC SHA-256 no header X-Lastreo-Signature: t=<ts>,v1=<hex> — valide com o seu segredo. Retry em 1m/5m/30m/2h/6h (5 tentativas). Eventos publicáveis: verification.approved/rejected/submitted, alert.created, assessment.created, symptom.recorded, medication_reminder.due, patient.created/merged/deleted, dsr.created, incident.notified. Zero impacto no consumidor App EM (sem webhook configurado, sem efeito). |
| 1.39 | 19/05/2026 | Observabilidade interna (rastreamento de erros, uso administrativo Lastreo). Zero impacto no consumidor. |
| 1.38 | 19/05/2026 | Segurança técnica. Dados sensíveis (CPF, telefone) criptografados em repouso; TLS no banco de dados; backup automatizado e criptografado com retenção. Endpoints GET /health (liveness) e GET /health/ready (readiness com checagem de DB). Zero impacto no consumidor. |
| 1.37 | 19/05/2026 | Consent tracking versionado (LGPD Art. 8 + 18 IX). Endpoints públicos: GET /consents/current (versão vigente por tipo) e GET /consents/{type}/{version} (conteúdo). Titular: POST /users/me/consents, POST /users/me/consents/{id}/revoke, GET /users/me/consents. Auto-consent no /auth/register para documentos vigentes. Zero impacto no consumidor. |
| 1.36 | 19/05/2026 | Notificação de incidente de segurança (LGPD Art. 48). O Lastreo, como operador, notifica os clientes afetados por um incidente de dados; o webhook incident.notified sinaliza o cliente em tempo real. Zero impacto no consumidor. |
| 1.35 | 19/05/2026 | Direitos do titular automatizados (LGPD Art. 18 + 20). Endpoints: POST /data-subject-requests (titular cria; alguns tipos são processados automaticamente), GET /users/me/data-subject-requests, GET /data-subject-requests/{id}; a fila é gerida pelo admin. GET /subprocessors lista pública de sub-operadores (Art. 18 VII). 9 direitos cobertos. Zero impacto no consumidor. |
| 1.34 | 19/05/2026 | Expansão do registro de auditoria interno em rotas críticas (forense + LGPD). Zero impacto no consumidor. |
| 1.33 | 19/05/2026 | Registro de auditoria (base de compliance LGPD). Endpoints: GET /admin/audit-log (admin) e GET /users/me/audit-log (titular — LGPD Art. 18 II + VII). Ações sensíveis ficam registradas com ator, alvo e timestamp, de forma imutável. Zero impacto no consumidor App EM. |
| 1.32 | 19/05/2026 | Isolamento cross-tenant aplicado em todos os endpoints clínicos secundários (assessments, health-registries, alerts, medication-reminders, exports, dashboards, gestão de pacientes). Zero impacto no consumidor App EM — as queries seguem retornando os mesmos resultados; acesso cross-tenant retorna 404. |
| 1.31 | 19/05/2026 | Paciente passa a ser escopado por cliente. Zero impacto no consumidor App EM: listagens e leituras de paciente filtram pelo cliente atual; acesso cross-tenant retorna 404. Médico permanece global (1 CRM = 1 conta Lastreo). Email de paciente único por cliente; email de médico único globalmente. |
| 1.30 | 19/05/2026 | Fundação multi-tenant interna. Zero impacto no consumidor — X-API-KEY continua idêntico; nenhum endpoint mudou shape de request/response. Compromisso de estabilidade /v1 documentado. |
| 1.29 | 19/05/2026 | Verificação profissional de médicos em dois caminhos: ouro (assinatura digital ICP-Brasil AR-CFM, aprovação imediata e automatizada) e prata (envio de foto da carteira CRM + selfie para análise manual em até 24h). verification_tier (strong/basic/null) adicionado a UserOut e ao status de verificação. Caminho ouro: POST /doctors/me/verification/terms (sem body — usa full_name do perfil), POST /doctors/me/verification/submit, GET /doctors/me/verification/status. Caminho prata: POST /doctors/me/verification/basic/submit, GET /doctors/me/verification/basic. Ver Fluxo 17 |
| 1.28 | 14/04/2026 | Perfil: UserOut agora inclui phone_number, date_of_birth, biological_sex, cpf, address_city, address_state, crm e medical_specialty (todos opcionais). Novos endpoints: PATCH /users/me (usuário edita próprio perfil), PATCH /doctors/me/patients/{id} (médico edita paciente provisório), POST /auth/change-email + POST /auth/confirm-email-change (troca de email autenticada com código de confirmação) |
| 1.27 | 05/04/2026 | Pacientes: médico agora pode remover paciente da lista (DELETE /doctors/me/patients/{id}). Se o paciente for provisório, é apagado junto com todos os seus dados clínicos; se for real, apenas desassocia (conta preservada) |
| 1.26 | 04/04/2026 | Pacientes: médico pode criar paciente provisório com apenas o nome (POST /doctors/me/patients/provisional) e mesclar com conta real posteriormente (POST /doctors/me/patients/{id}/merge). UserOut agora inclui is_provisional e email pode ser null. Bugfixes no pipeline de chat: tool calling corrigido, unicode corrigido em respostas de ferramentas, mensagens internas filtradas do histórico |
| 1.25 | 11/03/2026 | Chat: arquitetura split send + SSE — POST /send retorna imediatamente com ID, resposta da IA via GET /stream (Server-Sent Events). Suporte a local_id para idempotência e correlação cliente-servidor |
| 1.24 | 10/03/2026 | Chat: paginação por cursor (before_id/before), ordenação DESC (mais recente primeiro). Auth: endpoint de alteração de senha (change-password) |
| 1.23 | 10/03/2026 | Base URL atualizada para HTTPS com domínio estável (lastreo.com) |
| 1.22 | 10/03/2026 | Migração de servidor: nova Base URL, seção de conexão adicionada |
| 1.21 | 02/03/2026 | Push Notifications (FCM): Fluxo 16 — gerenciamento de device tokens para notificações push nativas Android/iOS |
| 1.20 | 25/02/2026 | Chat In-App: documentação sobre gerenciamento de contexto de conversa (janela deslizante + resumo automático) |
| 1.16 | 20/02/2026 | Chat In-App (POST /send, GET /history, DELETE /history — alternativa REST ao WhatsApp/Telegram para chat direto no App EM) |
| 1.15 | 20/02/2026 | Alertas clínicos (API + threshold automático + notificação médicos), lembretes de medicação (CRUD + scheduler), 4 novos tipos Health Connect (SLEEP, NUTRITION, HYDRATION, HEART_RATE), export FHIR R4 |
| 1.14 | 20/02/2026 | Health Registries: upsert via external_id (deduplicação Health Connect), DELETE individual e DELETE ALL (LGPD) |
| 1.13 | 19/02/2026 | Adicionados: Fluxo 10 (Analytics e Dashboards — dashboard do médico, tendências de sintomas, correlação saúde×exercício), Fluxo 11 (Export de Dados Clínicos — CSV e PDF) |
| 1.12 | 19/02/2026 | Adicionado: Fluxo 9 (Verificação de Email), campo email_verified na resposta do usuário, envio automático de código de verificação no registro |
| 1.11 | 13/02/2026 | Melhorias de performance e estabilidade no processamento de mensagens |
| 1.10 | 13/02/2026 | Adicionados: Fluxo 7 (Registros de Saúde / Health Connect), Fluxo 8 (Recuperação de Senha), endpoints de batch e summary para health registries |
| 1.09 | 08/12/2025 | Adicionados: campos EDSS atualizados, Fluxo 5 e 6 (Paciente e Médico) |
Base URL
https://lastreo.com
Todos os endpoints deste documento são relativos a esta URL. Exemplo: POST /auth/login → POST https://lastreo.com/auth/login
Nota: Conexões HTTP são automaticamente redirecionadas para HTTPS.
Estabilidade da API e compromisso com o consumidor
A partir da v1.30, o Lastreo é uma API B2B multi-tenant. Internamente, requisições são identificadas por aplicação cliente (via X-API-KEY), mas isso é transparente para o consumidor — seu código não precisa mudar para acompanhar essa evolução.
Compromissos hard que valem para qualquer release até o fim da vida da v1:
/api/v1é estável. Breaking changes (quando inevitáveis) virão como/api/v2com no mínimo 6 meses de aviso de depreciação./api/v1continua disponível.X-API-KEYpermanece o header de autenticação e o valor daSERVICE_API_KEYque você já usa continua válido indefinidamente.- Adições em respostas são sempre opcionais. Novos campos podem aparecer em
UserOut, etc. (ex:verification_tieradicionado na v1.29); renomeações e remoções nunca acontecem sem/v2. - Adições em requests são sempre opcionais. Nenhum endpoint existente passa a exigir parâmetros novos.
2. Primeiros Passos (Guia Rápido para Desenvolvedores)
Para começar a testar a API, siga estes dois passos simples para criar uma conta de teste e vincular seu canal de mensagens.
1. Crie sua Conta de Desenvolvedor:
* Use o endpoint POST /auth/register (descrito no Fluxo 1) para criar uma nova conta de paciente para você.
* Após o registro, faça login com POST /auth/login para obter seu access_token e refresh_token. Você precisará deles para gerenciar a sessão.
2. Vincule seu Canal de Mensagens:
* Com seu access_token em mãos, siga as instruções do Fluxo 2, escolhendo o guia específico para a plataforma que deseja vincular (Telegram ou WhatsApp).
3. Conceitos Fundamentais
Para entender como o Lastreo funciona, é importante conhecer alguns conceitos centrais da plataforma.
3.1. Perfis de Usuário
Existem dois tipos principais de usuários no sistema:
* Paciente (paciente): O perfil padrão. Representa o indivíduo cujos dados de saúde estão sendo monitorados. Pacientes podem registrar seus próprios sintomas e avaliações.
* Médico (medico): Um profissional de saúde. Este perfil tem permissões elevadas para visualizar os dados dos pacientes aos quais está associado e registrar avaliações clínicas, como a EDSS.
3.2. Contas Provisórias (Onboarding via Bot)
O Lastreo permite que um novo usuário comece a interagir com o bot no WhatsApp ou Telegram antes mesmo de criar uma conta completa no aplicativo. * Quando uma mensagem de um novo número é recebida, o sistema cria automaticamente uma conta provisória. * O usuário pode imediatamente começar a registrar sintomas e usar as funcionalidades básicas do bot. * Posteriormente, ao criar uma conta no aplicativo, o usuário pode "reivindicar" seu histórico de conversas através de um processo de verificação, unificando a conta provisória com sua nova conta completa (ver Fluxo 3).
3.3. Canais de Contato e Papéis
Um único usuário pode interagir com o Lastreo através de múltiplos canais (ex: chat in-app, WhatsApp e Telegram). Para máxima flexibilidade, especialmente para profissionais de saúde:
* Papel por Canal (channel_role): Um usuário com perfil de medico pode designar um papel específico para cada canal que vincula.
* Exemplo: Um médico pode vincular seu WhatsApp pessoal com channel_role: "paciente" para monitorar sua própria saúde, e vincular seu Telegram de trabalho com channel_role: "medico" para interagir com o Lastreo sobre seus pacientes.
* O bot Lastreo adaptará sua persona e suas funcionalidades com base no papel do canal que iniciou a conversa.
3.5. Verificação de Email (Novo na v1.12)
O Lastreo implementa verificação de email para garantir que os usuários possuem acesso ao email cadastrado. A verificação não bloqueia o acesso ao sistema — o usuário pode fazer login, acessar seu perfil, usar o chatbot, registrar sintomas, assessments e health registries normalmente.
Funcionalidades que requerem email verificado: * Vincular canais de contato (WhatsApp/Telegram) — Fluxos 2 e 3 * Funcionalidades exclusivas de médico (associar pacientes, etc.)
Funcionalidades que NÃO requerem email verificado: * Login, perfil, chat in-app, chatbot via mensageiros, sintomas, assessments, health registries
O campo email_verified (booleano) é retornado na resposta do GET /api/v1/users/me e indica se o email do usuário foi verificado. Ver Fluxo 9 para os endpoints de verificação.
3.4. Uma Estrutura Flexível para Dados de Saúde
O Lastreo utiliza dois modelos de dados para registros de saúde:
-
ClinicalAssessment: Registro central para avaliações clínicas formais (EDSS, Teste de Caminhada, SymptoMScreen, Surtos).assessment_type: Identifica o tipo de avaliação.data: Campo JSON flexível com dados validados pela API.
-
HealthRegistry: (Novo na v1.10, expandido na v1.15) Registro para dados de saúde contínuos vindos de dispositivos e apps de saúde (ex: Health Connect do Android).registry_type: Identifica o tipo de dado.data: Campo JSON com dados granulares (formato varia por tipo).external_id(v1.14): Identificador externo opcional para deduplicação. Quando fornecido, o POST funciona como upsert (cria ou atualiza).
Tipos de Health Registry e dados principais:
Tipo Representa Dado principal no dataUnidade STEPSContagem de passos data.detailedSteps.totalStepspassos (int) EXERCISE_SESSIONSessão de exercício físico data.exerciseSession.duration,data.exerciseSession.typeduração ISO 8601 (ex: PT35M)SLEEPPeríodo de sono data.totalDuration,data.startTime,data.endTimeduração ISO 8601 ou horas (float) NUTRITIONRefeição/ingestão alimentar data.mealType,data.calorieskcal (float) HYDRATIONIngestão de líquidos data.totalVolumeMlmililitros (int) HEART_RATEFrequência cardíaca ao longo do dia data.avgBpm,data.minBpm,data.maxBpmbpm (int) -
Alert: (Novo na v1.15) Alertas clínicos para médicos. Gerados automaticamente pelo sistema quando situações relevantes são detectadas. Os médicos associados ao paciente são notificados via seus canais ativos.source: Origem do alerta —ai_chat(conversa com IA),threshold(dados clínicos do paciente) oumanual(uso futuro).severity: Nível de urgência:
Severity Significado Urgência baixaSituação informativa, sem risco imediato O médico pode revisar quando conveniente mediaAtenção necessária, possível deterioração Recomenda-se revisão em breve altaSituação preocupante que requer avaliação O médico deve revisar o mais rápido possível symptom_record_id: Quando presente, referencia o registro de sintoma que originou o alerta. Permite navegar diretamente para o sintoma em questão.- Nota: Alertas são gerados de forma assíncrona. O frontend deve fazer polling periódico no endpoint
unread-countpara manter o badge atualizado.
-
MedicationReminder: (Novo na v1.15) Lembretes de medicação. O paciente recebe mensagens automáticas nos horários configurados via seus canais ativos (WhatsApp/Telegram).reminder_times: Lista de horários em formato HH:MM (ex:["08:00", "20:00"]). Horários em UTC.frequency: Define em quais dias o lembrete é enviado:
Frequency Significado dailyTodos os dias twice_dailyTodos os dias (use com 2 horários em reminder_times)weeklyUma vez por semana (no mesmo dia da semana em que foi criado) weekdaysSegunda a sexta-feira is_active: Controla se o lembrete está ativo. Desativar (is_active: falsevia PATCH) mantém o registro para histórico; deletar (DELETE) remove permanentemente.
Os registros de saúde são salvos de forma granular (cada caminhada individual, cada sessão). A agregação (soma de passos por dia, média semanal) é feita na consulta via endpoint de summary.
4. Fluxos da API (Referência Técnica)
Fluxo 1: Autenticação de Usuário
Este fluxo descreve o ciclo de vida completo da autenticação, incluindo o login e a renovação de sessão.
1.1. Registro de Novo Usuário
- Endpoint:
POST /auth/register -
Corpo da Requisição:
json { "email": "[email protected]", "password": "Uma_Senha_Forte_123!", "full_name": "Nome Completo do Usuário", "role": "paciente" }> Dica para Testes (Criar Médico): > Para testar as funcionalidades exclusivas do perfil médico, você pode criar uma conta enviando"role": "medico"no corpo da requisição.Requisitos de Senha: * Mínimo de 8 caracteres * Pelo menos uma letra maiúscula * Pelo menos uma letra minúscula * Pelo menos um número * Pelo menos um caractere especial
Verificação de Email: (Novo na v1.12) Após o registro bem-sucedido, o backend envia automaticamente um código de verificação de 6 dígitos para o email cadastrado. O usuário pode verificar seu email seguindo o Fluxo 9. A verificação de email não bloqueia o acesso ao sistema, mas é necessária para certas funcionalidades (ver seção 3.5).
1.2. Login de Usuário
- Endpoint:
POST /auth/login - Rate Limit: 5 requisições por minuto.
- Corpo da Requisição:
json { "email": "[email protected]", "password": "uma_senha_muito_forte" } - Resposta de Sucesso:
json { "access_token": "eyJ... (vida curta: 60 minutos)", "refresh_token": "a1b2c3d4... (vida longa: 30 dias)", "token_type": "bearer" } - Nota sobre Erros: Se as credenciais estiverem incorretas, a API retornará
401 Unauthorized.
1.3. Uso do Access Token
- Para todas as chamadas autenticadas aos endpoints
/api/v1/*, inclua o cabeçalho:Authorization: Bearer <seu_access_token>
1.4. Gerenciamento de Sessão e Renovação de Token (Refresh Token)
Para equilibrar segurança e uma boa experiência de usuário, utilizamos um sistema de dois tokens.
access_token: Tem vida curta (60 minutos) e é usado para autenticar cada chamada à API. Sua curta duração minimiza os riscos em caso de interceptação.refresh_token: Tem vida longa (30 dias) e sua única finalidade é obter um novo par de tokens quando oaccess_tokenexpirar.
Fluxo de Renovação (Ação do Frontend):
-
Armazenamento: Após o login, o
access_tokenpode ser guardado em memória, enquanto orefresh_tokendeve ser armazenado de forma segura e persistente no dispositivo (ex: Keychain/Keystore). -
Detecção de Expiração: Quando uma chamada à API falhar com o status
401 Unauthorized, o frontend deve assumir que oaccess_tokenexpirou. -
Renovação Silenciosa: Em vez de deslogar o usuário, o frontend deve fazer uma chamada para o novo endpoint de renovação:
- Endpoint:
POST /auth/refresh - Corpo da Requisição:
json { "refresh_token": "<o_refresh_token_salvo_no_dispositivo>" }
- Endpoint:
-
Recebimento de Novos Tokens: O backend responderá com um novo par de
access_tokenerefresh_token.- Resposta de Sucesso:
json { "access_token": "NOVO_eyJ...", "refresh_token": "NOVO_a1b2c3d4...", "token_type": "bearer" }
- Resposta de Sucesso:
-
Atualização e Retentativa: O frontend deve substituir os tokens antigos pelos novos e, em seguida, tentar novamente a chamada original à API que havia falhado.
-
Falha na Renovação: Se a chamada para
/auth/refreshtambém falhar com401, significa que a sessão do usuário realmente expirou. Neste caso, o usuário deve ser deslogado e redirecionado para a tela de login.
1.5. Troca de Email (Novo na v1.28)
A alteração do email associado à conta é feita em duas etapas (solicitação + confirmação por código enviado ao novo email), protegendo contra mudanças acidentais ou mal-intencionadas. O endpoint PATCH /users/me não permite trocar email — use este fluxo.
-
Passo 1: Solicitar a Troca (App -> Backend)
- Endpoint:
POST /auth/change-email - Rate Limit: 3 requisições por hora.
- Corpo da Requisição:
json { "new_email": "[email protected]", "password": "senha_atual_do_usuario" } - Resposta de Sucesso (200):
json { "message": "Um código de confirmação foi enviado para o novo email." } - Ação do Backend: Valida a senha, verifica que o novo email não está em uso e envia um código de 6 dígitos para
new_email. O email da conta não é alterado nesta etapa. - Erros:
400— senha incorreta, novo email igual ao atual, ou novo email já em uso.
- Endpoint:
-
Passo 2: Confirmar a Troca (App -> Backend)
- Endpoint:
POST /auth/confirm-email-change - Rate Limit: 10 requisições por hora.
- Corpo da Requisição:
json { "code": "123456" } - Resposta de Sucesso (200):
json { "message": "Email alterado com sucesso. Faça login novamente." } - Ação do Backend: Valida o código, atualiza o email, marca o email como verificado (
email_verified: true) e revoga todos os refresh tokens ativos do usuário. Oaccess_tokenem uso deixa de funcionar (o camposubdo JWT referencia o email antigo), portanto o frontend deve forçar um novo login após a confirmação. - Erros:
400— código inválido, expirado, já usado, ou o novo email foi tomado por outro cadastro entre os dois passos.
- Endpoint:
Resumo para o frontend: após confirm-email-change retornar 200, limpe os tokens locais e redirecione para a tela de login.
Fluxo 2: Vinculação de Canal de Contato
Este fluxo é iniciado pelo usuário dentro do aplicativo.
2.1. Vinculação via Telegram (Fluxo com Deep Link)
Princípio: O usuário deve iniciar a conversa com o bot. A confirmação é feita inteiramente dentro do Telegram.
-
Passo 1: Obter o Código de Vinculação (App -> Backend)
- Endpoint:
POST /api/v1/channels/link-request - Corpo da Requisição:
json { "platform": "telegram" } - Resposta do Backend:
json { "platform": "telegram", "verification_code": "X4T7B" }
- Endpoint:
-
Passo 2: Redirecionar para o Telegram (App)
- O frontend constrói e abre a seguinte URL:
https://t.me/LastreoBot?start=X4T7B(substituindoLastreoBote o código).
- O frontend constrói e abre a seguinte URL:
-
Passo 3: Confirmação no Telegram (Usuário -> Bot -> Backend)
- O usuário clica no botão "Começar" no Telegram.
- O Telegram envia a mensagem
/start X4T7Bpara o nosso bot. - O backend recebe esta mensagem, valida o código e finaliza a vinculação automaticamente. O frontend não precisa fazer mais nada.
2.2. Vinculação via WhatsApp (Fluxo com Código de Verificação)
Princípio: O backend pode iniciar uma conversa enviando um código para o usuário.
-
Passo 1: Solicitar o Código de Verificação (App -> Backend)
- O usuário digita seu número de telefone no aplicativo.
- Endpoint:
POST /api/v1/channels/link-request - Corpo da Requisição:
json { "platform": "whatsapp", "contact_id": "5511999998888" } - Ação do Backend: Envia uma mensagem para o WhatsApp do usuário com um código de 6 dígitos.
-
Passo 2: Validar o Código (App -> Backend)
- O usuário recebe o código no WhatsApp, volta para o aplicativo e o digita.
- Endpoint:
POST /api/v1/channels/link-confirm - Corpo da Requisição:
json { "platform": "whatsapp", "contact_id": "5511999998888", "code": "123456", "channel_role": "paciente" } - Ação do Backend: Valida o código e, se correto, finaliza a vinculação.
Fluxo 3: Onboarding do Bot e Reivindicação de Conta
Distinção Importante: Este fluxo é diferente da "Vinculação de Canal" (Fluxo 2). * Vinculação (Fluxo 2): Para um usuário que já tem uma conta completa e quer adicionar um novo canal de contato que nunca foi usado antes. * Reivindicação (Fluxo 3): É uma ação única para um usuário que acabou de criar sua conta completa e quer "puxar" o histórico de um canal que ele já usava com uma conta provisória.
Este fluxo conecta o histórico de um usuário que começou pelo bot a uma conta completa criada no aplicativo.
-
Passo 1: Solicitar a Reivindicação (App -> Backend)
- O usuário, já logado no app, informa o número de telefone que usou para falar com o bot.
- Endpoint:
POST /api/v1/users/claim-request - Corpo da Requisição:
json { "platform": "whatsapp", "contact_id": "5511999998888" } - Ação do Backend: Envia um código de verificação para o canal de contato, se ele estiver associado a uma conta provisória.
-
Passo 2: Confirmar a Reivindicação (App -> Backend)
- O usuário insere o código recebido no aplicativo.
- Endpoint:
POST /api/v1/users/claim-confirm - Corpo da Requisição:
json { "platform": "whatsapp", "contact_id": "5511999998888", "code": "654321" } - Ação do Backend: Valida o código, migra todo o histórico (sintomas, registros de saúde, etc.) da conta provisória para a conta completa do usuário e apaga a conta provisória.
Tratamento de Conflito (Número Reciclado)
Se o usuário tentar vincular um número que já possui uma conta provisória (retorno 400), o frontend deve oferecer duas opções:
- Importar Histórico: Segue o fluxo de Reivindicação descrito acima.
- Começar do Zero (Número Reciclado): Se o usuário disser que não quer os dados antigos (ex: comprou um chip novo), o frontend deve chamar novamente o endpoint de Vinculação (
/link-confirm), mas adicionando o campo"force_link": true.- Isso apagará a conta provisória antiga e vinculará o número à conta atual do usuário, garantindo a privacidade e limpando dados de terceiros.
Fluxo 4: Registro de Dados Clínicos
Este é o fluxo principal para registrar qualquer tipo de avaliação de saúde.
- Endpoint:
POST /api/v1/patients/{patient_id}/assessments -
Permissões:
- Um paciente só pode chamar este endpoint com seu próprio
patient_id. - Um médico pode chamar este endpoint para qualquer
patient_idao qual esteja associado.
- Um paciente só pode chamar este endpoint com seu próprio
-
Corpo da Requisição (Exemplo 1: Teste de Caminhada)
json { "assessment_type": "WALK_TEST", "data": { "time_seconds": 9.2 }, "timestamp": "2025-11-13T10:30:00Z" } -
Corpo da Requisição (Exemplo 2: Início de um Surto)
json { "assessment_type": "OUTBREAK", "data": { "description": "Forte dormência na perna esquerda e fadiga intensa." } } -
Corpo da Requisição (Exemplo 3: Avaliação EDSS por um Médico)
json { "assessment_type": "EDSS", "data": { "fs_pyramidal": 2, "fs_cerebellar": 3, "fs_brainstem": 1, "fs_sensory": 2, "fs_bowel_bladder": 0, "fs_visual": 1, "fs_cerebral": 0, "ambulation_score": 5, "total_score": 4.5 } } -
Corpo da Requisição (Exemplo 4: Escala de Sintomas - SymptoMScreen)
json { "assessment_type": "SYMPTOM_SCREEN_SCALE", "data": { "to_walk": 1, "manual_skills": 0, "spasticity_and_rigidity": 2, "body_pain": 0, "sensory_symptoms": 1, "bladder_control": 0, "fatigue": 3, "vision": 0, "dizziness": 0, "cognitive_function": 1, "depression": 0, "anxiety": 0 }, "timestamp": "2025-11-21T10:30:00Z" }
Fluxo 5: Funcionalidades do Paciente
Endpoints para um paciente gerenciar sua própria conta e visualizar seus dados.
-
Obter Informações do Usuário Logado:
GET /api/v1/users/me- Resposta (
UserOut):json { "id": 5, "email": "[email protected]", "full_name": "Maria Silva", "role": "paciente", "email_verified": true, "is_provisional": false, "phone_number": "+5511999998888", "date_of_birth": "1985-06-15", "biological_sex": "feminino", "cpf": "12345678909", "address_city": "São Paulo", "address_state": "SP", "crm": null, "medical_specialty": null, "verification_status": null } - O campo
roleserá"paciente"ou"medico". Útil para controlar a navegação/UI do app. - O campo
is_provisionalindica se é um registro provisório criado pelo médico (pacientes normais vêm comofalse). Em pacientes provisórios,emailpode sernull. - Os campos de perfil são opcionais e podem vir
nullaté serem preenchidos viaPATCH /users/me. biological_sexaceita:"masculino","feminino","outro".crmemedical_specialtysão preenchidos apenas por usuários comrole = "medico".verification_statussó é relevante para médicos:"pending"(recém-registrado),"approved"(verificado via AR-CFM — ver Fluxo 17),"rejected"(última tentativa falhou). Para pacientes, vemnull. Frontend deve checar esse campo ao abrir o app: médicos compending/rejecteddevem ser direcionados ao fluxo de verificação antes de usar funcionalidades clínicas.
-
Editar Perfil do Usuário Logado: (novo na v1.28)
PATCH /api/v1/users/me- Atualiza os dados cadastrais do próprio usuário. Somente os campos enviados no corpo são alterados (semântica PATCH — enviar
nulllimpa o campo). - Campos editáveis:
full_name,phone_number,date_of_birth(ISO 8601 —YYYY-MM-DD),biological_sex(masculino|feminino|outro),cpf(11 dígitos ou000.000.000-00— é armazenado sem formatação),address_city,address_state(2 letras — ex:SP),crm,medical_specialty. - Campos NÃO editáveis por este endpoint:
email(use o fluxo de troca de email — Fluxo 1.5),role,is_provisional,email_verified,id. - Corpo da Requisição (exemplo):
json { "full_name": "Maria da Silva", "phone_number": "+5511999990000", "date_of_birth": "1985-06-15", "biological_sex": "feminino", "address_city": "São Paulo", "address_state": "SP" } - Resposta (200): objeto
UserOutcompleto atualizado. - Erros:
400— paciente tentando definircrmoumedical_specialty(campos exclusivos de médicos).422— validação falhou (ex: CPF mal formatado, UF inválida, campo desconhecido,full_namemuito curto).
-
Obter Perfil do Paciente (por ID):
GET /api/v1/patients/{patient_id}
- Listar Seus Canais Vinculados:
GET /api/v1/channels/me
- Desvincular um Canal:
DELETE /api/v1/channels/{channel_id}
- Listar Todas as Suas Avaliações Clínicas:
GET /api/v1/patients/{seu_user_id}/assessments
- Listar Todos os Seus Registros de Sintomas:
GET /api/v1/patients/{seu_user_id}/symptoms
- Listar Seus Registros de Saúde (Health Connect):
GET /api/v1/patients/{seu_user_id}/health-registries
Fluxo 6: Funcionalidades do Médico
Endpoints para um médico gerenciar e visualizar os dados de seus pacientes.
-
Associar um Paciente Existente a um Médico:
POST /api/v1/doctors/me/patients- Corpo da Requisição:
json { "patient_email": "[email protected]" }
-
Criar Paciente Provisório:
POST /api/v1/doctors/me/patients/provisional- Permite ao médico criar um registro de paciente com apenas o nome, sem que o paciente precise se cadastrar.
- O paciente provisório aparece normalmente na lista de pacientes e pode receber avaliações clínicas.
- Posteriormente, quando o paciente se cadastrar no app, o médico pode mesclar os dados usando o endpoint de merge.
- Corpo da Requisição:
json { "full_name": "Nome do Paciente" } - Resposta (201):
json { "id": 5, "email": null, "full_name": "Nome do Paciente", "role": "paciente", "email_verified": false, "is_provisional": true }
-
Mesclar Paciente Provisório com Conta Real:
POST /api/v1/doctors/me/patients/{provisional_id}/merge- Transfere todos os dados (avaliações, sintomas, registros de saúde, alertas, lembretes) do paciente provisório para a conta real do paciente.
- Remove o paciente provisório após a mesclagem.
- Corpo da Requisição:
json { "patient_email": "[email protected]" } - Resposta (200):
json { "message": "Dados mesclados com sucesso. Paciente 'Nome Real' vinculado.", "patient_id": 3 }
-
Editar Dados de um Paciente Provisório: (novo na v1.28)
PATCH /api/v1/doctors/me/patients/{patient_id}- Permite ao médico atualizar os dados cadastrais de um paciente provisório da sua lista. Os mesmos campos de
PATCH /users/mepodem ser enviados, excetocrmemedical_specialty(campos exclusivos de médicos). - Para pacientes reais, este endpoint retorna
403— o próprio paciente deve atualizar seu perfil viaPATCH /users/me. - Corpo da Requisição (exemplo):
json { "full_name": "João Paulo de Souza", "date_of_birth": "1970-01-30", "biological_sex": "masculino", "phone_number": "+5511988887777" } - Resposta (200): objeto
UserOutatualizado. - Erros:
403— usuário não é médico, ou o paciente é real (não-provisório).404— paciente não está na lista do médico autenticado.422— validação falhou.
-
Remover Paciente da Lista do Médico: (novo na v1.27)
DELETE /api/v1/doctors/me/patients/{patient_id}- Remove um paciente da lista do médico autenticado. O comportamento depende do tipo de paciente:
- Paciente provisório: o paciente é apagado junto com todos os seus dados clínicos (avaliações, sintomas, registros de saúde, alertas e lembretes de medicação), já que ele só existe por causa deste médico.
- Paciente real: apenas a associação médico-paciente é removida. A conta do paciente e todos os seus dados são preservados — ele continua podendo usar o app normalmente e pode ser associado a outros médicos.
- Resposta (200) — paciente provisório:
json { "message": "Paciente provisório 'Nome' removido com sucesso.", "deleted": true } - Resposta (200) — paciente real:
json { "message": "Paciente 'Nome' desvinculado com sucesso.", "deleted": false } - Erros:
403— usuário não é médico404— paciente não está na lista do médico autenticado
-
Listar Todos os Pacientes de um Médico:
GET /api/v1/doctors/me/patients- Resposta: Lista de objetos
UserOutcompletos (incluindophone_number,date_of_birth,biological_sex,cpf,address_city,address_state— todos opcionais), um por paciente associado. - Pacientes provisórios têm
is_provisional: trueeemail: null.
-
Buscar por Pacientes:
GET /api/v1/patients/search?query={nome_ou_email}- Permite que um médico encontre pacientes no sistema para facilitar a associação.
-
Obter Perfil de um Paciente:
GET /api/v1/patients/{patient_id}- Retorna os dados cadastrais do paciente (nome, email, etc).
-
Visualizar Dados de um Paciente Específico:
- Um médico autenticado pode usar os endpoints existentes, passando o ID do paciente desejado:
GET /api/v1/patients/{patient_id}/symptomsGET /api/v1/patients/{patient_id}/assessmentsGET /api/v1/patients/{patient_id}/health-registries
- Um médico autenticado pode usar os endpoints existentes, passando o ID do paciente desejado:
-
Registrar uma Avaliação para um Paciente:
POST /api/v1/patients/{patient_id}/assessments(conforme Fluxo 4).
Fluxo 7: Registros de Saúde (Health Connect) (Atualizado na v1.17)
Este fluxo gerencia dados de saúde contínuos vindos do Health Connect. Tipos suportados: STEPS, EXERCISE_SESSION, SLEEP, NUTRITION, HYDRATION, HEART_RATE. Os registros são armazenados de forma granular — cada caminhada individual ou sessão de exercício é um registro separado.
Formato flat (v1.17): Todos os dados são enviados em formato flat dentro de data, sem wrappers intermediários. O campo data.uid é usado como chave de upsert (deduplicação automática) e data.last_modified para resolução de conflito de versão.
7.1. Enviar um Registro de Saúde (Individual / Upsert)
- Endpoint:
POST /api/v1/patients/{patient_id}/health-registries - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
- Comportamento de Upsert: Se
data.uidestiver presente, o backend automaticamente faz upsert — se já existir um registro com o mesmouidpara o mesmo paciente, os dados são atualizados em vez de criar duplicata. -
Resolução de conflito: Se
data.last_modifiedestiver presente, o registro só é atualizado se olast_modifiedincoming for mais recente que o armazenado. Dados mais antigos são silenciosamente ignorados (o registro retornado contém os dados atuais). -
Corpo da Requisição (Passos):
json { "type": "STEPS", "data": { "uid": "hc-steps-2026-02-13", "start_time": "2026-02-13T06:00:00Z", "end_time": "2026-02-13T08:00:00Z", "count": 3421, "source_app_package": "com.google.android.apps.fitness", "device_model": "Pixel 8", "last_modified": "2026-02-13T08:05:00Z" }, "timestamp": "2026-02-13T08:00:00Z" }Campo Tipo Obrigatório Descrição uidstring Sim Identificador único do registro (chave de upsert) start_timestring (ISO 8601) Sim Início do período de contagem end_timestring (ISO 8601) Sim Fim do período de contagem countint Sim Total de passos no período source_app_packagestring Não Package do app que gerou o dado device_modelstring Não Modelo do dispositivo last_modifiedstring (ISO 8601) Não Data da última modificação (para conflito de versão) -
Corpo da Requisição (Sessão de Exercício):
json { "type": "EXERCISE_SESSION", "data": { "uid": "session-456", "type_exercise": "RUNNING", "start_time": "2026-02-13T07:00:00Z", "end_time": "2026-02-13T07:35:00Z", "duration": "PT35M", "total_distance_meters": 4500.0, "avg_speed_kmh": 7.7, "avg_bpm": 145, "min_bpm": 110, "max_bpm": 172, "last_modified": "2026-02-13T07:40:00Z", "heart_rate_series": [ {"time": "2026-02-13T07:00:00Z", "bpm": 110}, {"time": "2026-02-13T07:10:00Z", "bpm": 145}, {"time": "2026-02-13T07:20:00Z", "bpm": 160} ] }, "timestamp": "2026-02-13T07:35:00Z" }Campo Tipo Obrigatório Descrição uidstring Sim Identificador único da sessão type_exercisestring Não Tipo de exercício (RUNNING, WALKING, etc.) start_timestring (ISO 8601) Sim Início da sessão end_timestring (ISO 8601) Sim Fim da sessão durationstring (ISO 8601 duration) Não Duração total (ex: "PT35M") total_distance_metersfloat Não Distância total em metros avg_speed_kmhfloat Não Velocidade média em km/h max_speed_kmhfloat Não Velocidade máxima em km/h avg_pace_min_per_kmfloat Não Ritmo médio (min/km) min_pace_min_per_kmfloat Não Melhor ritmo (min/km) avg_bpmint Não FC média min_bpmint Não FC mínima max_bpmint Não FC máxima last_modifiedstring (ISO 8601) Não Data da última modificação heart_rate_zonesarray Não Zonas de FC hr_zone_seriesarray Não Série temporal de zonas speed_seriesarray Não Série temporal de velocidade distance_seriesarray Não Série temporal de distância heart_rate_seriesarray Não Série temporal de FC -
Corpo da Requisição (Sono):
json { "type": "SLEEP", "data": { "totalDuration": "PT7H30M", "startTime": "2026-02-12T23:00:00Z", "endTime": "2026-02-13T06:30:00Z", "stages": [ {"stage": "LIGHT", "startTime": "2026-02-12T23:00:00Z", "endTime": "2026-02-13T00:30:00Z"}, {"stage": "DEEP", "startTime": "2026-02-13T00:30:00Z", "endTime": "2026-02-13T02:00:00Z"} ] }, "timestamp": "2026-02-13T06:30:00Z" } -
Corpo da Requisição (Nutrição):
json { "type": "NUTRITION", "data": { "mealType": "almoco", "calories": 650.0, "nutrients": {"protein": 35.0, "carbs": 80.0, "fat": 20.0} }, "timestamp": "2026-02-13T12:30:00Z" } -
Corpo da Requisição (Hidratação):
json { "type": "HYDRATION", "data": { "totalVolumeMl": 2500, "records": [ {"time": "2026-02-13T08:00:00Z", "volumeMl": 500}, {"time": "2026-02-13T12:00:00Z", "volumeMl": 750} ] }, "timestamp": "2026-02-13T22:00:00Z" } -
Corpo da Requisição (Frequência Cardíaca):
json { "type": "HEART_RATE", "data": { "avgBpm": 72, "minBpm": 55, "maxBpm": 110, "samples": [ {"time": "2026-02-13T08:00:00Z", "bpm": 65}, {"time": "2026-02-13T12:00:00Z", "bpm": 78}, {"time": "2026-02-13T18:00:00Z", "bpm": 72} ] }, "timestamp": "2026-02-13T23:00:00Z" } -
Resposta de Sucesso (201):
json { "id": 42, "patient_user_id": 5, "type": "STEPS", "data": { ... }, "timestamp": "2026-02-13T08:00:00Z", "external_id": "hc-steps-2026-02-13", "last_modified": "2026-02-13T08:05:00Z" }> Nota: O campoexternal_idna resposta corresponde aodata.uidenviado. O campolast_modifiedé extraído automaticamente dedata.last_modified.
7.2. Enviar Registros em Lote (Batch)
Para sincronização eficiente, especialmente quando o Health Connect acumula vários registros entre sincronizações.
- Endpoint:
POST /api/v1/patients/{patient_id}/health-registries/batch - Corpo da Requisição:
json { "registries": [ { "type": "STEPS", "data": { "uid": "hc-steps-2026-02-12", "start_time": "2026-02-12T06:00:00Z", "end_time": "2026-02-12T07:00:00Z", "count": 1500, "last_modified": "2026-02-12T07:05:00Z" }, "timestamp": "2026-02-12T07:00:00Z" }, { "type": "STEPS", "data": { "uid": "hc-steps-2026-02-13", "start_time": "2026-02-13T08:00:00Z", "end_time": "2026-02-13T09:00:00Z", "count": 2000, "last_modified": "2026-02-13T09:05:00Z" }, "timestamp": "2026-02-13T09:00:00Z" } ] } - Resposta: Lista com todos os registros criados/atualizados (mesmo formato do endpoint individual).
7.3. Listar Registros de Saúde (com Filtros)
- Endpoint:
GET /api/v1/patients/{patient_id}/health-registries -
Query Parameters (todos opcionais):
start_date— Data/hora inicial (ISO 8601). Ex:2026-02-01T00:00:00Zend_date— Data/hora final (ISO 8601). Ex:2026-02-13T23:59:59Zregistry_type— Filtrar por tipo:STEPS,EXERCISE_SESSION,SLEEP,NUTRITION,HYDRATION,HEART_RATE
-
Exemplo de Chamada:
GET /api/v1/patients/5/health-registries?start_date=2026-02-01T00:00:00Z&end_date=2026-02-07T23:59:59Z®istry_type=STEPS -
Resposta: Lista de registros ordenados por timestamp (mais recente primeiro).
7.4. Resumo Agregado (Summary)
Retorna totais agregados por período, ideal para gráficos e dashboards.
- Endpoint:
GET /api/v1/patients/{patient_id}/health-registries/summary -
Query Parameters:
start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)period— Granularidade:daily,weeklyoumonthly(padrão:daily)
-
Exemplo de Chamada:
GET /api/v1/patients/5/health-registries/summary?start_date=2026-02-01T00:00:00Z&end_date=2026-02-07T23:59:59Z&period=daily -
Resposta de Sucesso:
json { "start_date": "2026-02-01T00:00:00Z", "end_date": "2026-02-07T23:59:59Z", "period": "daily", "steps": { "total": 45230, "periods_count": 7, "by_period": [ {"date": "2026-02-01", "total_steps": 6500, "records_count": 3}, {"date": "2026-02-02", "total_steps": 7200, "records_count": 4}, {"date": "2026-02-03", "total_steps": 5100, "records_count": 2} ] }, "exercise_sessions": { "total_sessions": 3, "by_period": [ {"date": "2026-02-01", "sessions_count": 1, "total_duration_minutes": 35.0}, {"date": "2026-02-03", "sessions_count": 2, "total_duration_minutes": 60.0} ] }, "sleep": { "total_hours": 52.5, "periods_count": 7, "by_period": [ {"date": "2026-02-01", "total_hours": 7.5, "records_count": 1}, {"date": "2026-02-02", "total_hours": 8.0, "records_count": 1} ] }, "nutrition": { "total_calories": 14200.0, "periods_count": 7, "by_period": [ {"date": "2026-02-01", "total_calories": 2100.0, "records_count": 3} ] }, "hydration": { "total_ml": 17500, "periods_count": 7, "by_period": [ {"date": "2026-02-01", "total_ml": 2500, "records_count": 1} ] }, "heart_rate": { "avg_bpm": 72, "periods_count": 7, "by_period": [ {"date": "2026-02-01", "avg_bpm": 70, "records_count": 2} ] } } - Nota: Campos de tipos sem dados no período retornam
nullao invés de objetos vazios.
7.5. Remover um Registro de Saúde por UID (Atualizado na v1.17)
Remove um registro de saúde específico pelo uid (o mesmo valor usado no data.uid na criação).
- Endpoint:
DELETE /api/v1/patients/{patient_id}/health-registries/{uid} - Permissões: Apenas o próprio paciente. Médicos NÃO podem deletar registros de paciente (segurança clínica).
- Resposta:
204 No Content - Erros:
403— Usuário não é o paciente dono do registro.404— Registro não encontrado para este UID.
- Exemplo:
DELETE /api/v1/patients/5/health-registries/hc-steps-2026-02-13
7.6. Remover TODOS os Registros de Saúde (LGPD)
Remove todos os registros de saúde de um paciente. Útil para compliance com LGPD (direito ao apagamento).
- Endpoint:
DELETE /api/v1/patients/{patient_id}/health-registries - Permissões: Apenas o próprio paciente.
- Resposta de Sucesso (200):
json { "deleted_count": 15, "message": "15 registro(s) de saúde removido(s) com sucesso." } - Erros:
403— Usuário não é o paciente dono dos registros.
Fluxo 8: Recuperação de Senha (Novo na v1.10)
Permite que um usuário redefina sua senha caso a tenha esquecido.
8.1. Solicitar Código de Recuperação
- Endpoint:
POST /auth/forgot-password - Rate Limit: 3 requisições por hora.
- Corpo da Requisição:
json { "email": "[email protected]" } - Resposta (sempre a mesma, por segurança):
json { "message": "Se o email estiver cadastrado, você receberá um código de recuperação." } - Ação do Backend: Se o email existir no sistema, envia um código de 6 dígitos por email com validade de 15 minutos.
8.2. Redefinir Senha com Código
- Endpoint:
POST /auth/reset-password - Corpo da Requisição:
json { "email": "[email protected]", "code": "482951", "new_password": "Nova_Senha_Segura_456!" } - Resposta de Sucesso:
json { "message": "Senha redefinida com sucesso. Faça login com sua nova senha." } - Ações do Backend:
- Valida o código e verifica se não expirou.
- Atualiza a senha do usuário (mesmas regras de validação do registro).
- Invalida todos os refresh tokens do usuário (força re-login em todos os dispositivos).
- Erros:
400 Bad Request: Código inválido ou expirado.
Fluxo Completo para o Frontend (Esqueci a Senha):
1. Usuário clica em "Esqueci minha senha" na tela de login.
2. Frontend solicita o email e chama POST /auth/forgot-password.
3. Exibe tela para inserir o código de 6 dígitos.
4. Usuário insere o código + nova senha, frontend chama POST /auth/reset-password.
5. Redireciona para login em caso de sucesso.
8.3. Alterar Senha (Usuário Autenticado) (Novo na v1.24)
Para quando o usuário sabe a senha atual e deseja trocá-la (ex: tela de configurações do app).
- Endpoint:
POST /auth/change-password - Permissões: Qualquer usuário autenticado (Bearer Token).
- Rate Limit: 5 requisições por hora.
- Corpo da Requisição:
json { "current_password": "SenhaAtual@1234", "new_password": "NovaSenha@5678" } - Resposta de Sucesso (200):
json { "message": "Senha alterada com sucesso." } - Validação da nova senha: Mesmas regras do registro (8+ caracteres, maiúscula, minúscula, número, especial).
- Erros:
400 Bad Request: Senha atual incorreta.401 Unauthorized: Token inválido ou ausente.422 Unprocessable Entity: Nova senha não atende aos requisitos de complexidade.
Fluxo Completo para o Frontend (Alterar Senha):
1. Na tela de configurações, usuário clica em "Alterar senha".
2. Frontend solicita senha atual + nova senha + confirmação da nova senha.
3. Frontend chama POST /auth/change-password com Bearer Token.
4. Exibe mensagem de sucesso ou erro.
Fluxo 9: Verificação de Email (Novo na v1.12)
A verificação de email confirma que o usuário possui acesso ao email cadastrado. Um código de verificação é enviado automaticamente após o registro (Fluxo 1.1). O usuário também pode solicitar um novo código a qualquer momento.
9.1. Solicitar Código de Verificação
- Endpoint:
POST /auth/send-verification - Rate Limit: 3 requisições por hora.
- Corpo da Requisição:
json { "email": "[email protected]" } - Resposta (sempre a mesma, por segurança):
json { "message": "Se o email estiver cadastrado, você receberá um código de verificação." } - Ação do Backend: Se o email existir e ainda não estiver verificado, envia um código de 6 dígitos por email com validade de 10 minutos.
9.2. Verificar Email com Código
- Endpoint:
POST /auth/verify-email - Rate Limit: 10 requisições por hora.
- Corpo da Requisição:
json { "email": "[email protected]", "code": "482951" } - Resposta de Sucesso:
json { "message": "Email verificado com sucesso." } - Erros:
400 Bad Request: Código inválido ou expirado.
Fluxo Completo para o Frontend:
1. Após o registro, o backend já envia automaticamente um código de verificação.
2. Frontend exibe tela para inserir o código de 6 dígitos (pode ser na primeira tela após o registro ou em configurações).
3. Usuário insere o código, frontend chama POST /auth/verify-email.
4. Em caso de sucesso, atualizar o estado local do email_verified para true.
5. Se o código expirou, o usuário pode solicitar um novo com POST /auth/send-verification.
Nota: A verificação de email não é bloqueante — o usuário pode navegar e usar a maioria das funcionalidades sem verificar. A verificação é necessária apenas para vincular canais (WhatsApp/Telegram) e funcionalidades de médico. Ver seção 3.5 para detalhes.
Fluxo 10: Analytics e Dashboards (Novo na v1.13)
Endpoints para visualização de dados agregados, tendências e correlações. Úteis para dashboards no app.
10.1. Dashboard do Médico
Visão geral consolidada dos pacientes do médico: totais, atividade recente e últimos sintomas reportados.
- Endpoint:
GET /api/v1/doctors/me/dashboard - Permissões: Apenas médicos.
-
Query Parameters (opcionais):
days— Janela de atividade recente em dias (padrão:30)
-
Resposta de Sucesso:
json { "total_patients": 12, "active_patients_30d": 8, "symptoms_this_month": 45, "assessments_this_month": 6, "recent_symptoms": [ { "patient_id": 5, "patient_name": "Maria Silva", "symptom": "Fadiga", "intensity": 7, "timestamp": "2026-02-19T14:30:00Z" }, { "patient_id": 8, "patient_name": "João Santos", "symptom": "Dormência", "intensity": 5, "timestamp": "2026-02-19T10:15:00Z" } ] } - Os
recent_symptomsretornam os últimos 10 sintomas de todos os pacientes do médico, ordenados por data (mais recente primeiro).
10.2. Tendências de Sintomas do Paciente
Análise de tendências: sintomas mais frequentes e evolução ao longo do tempo.
- Endpoint:
GET /api/v1/patients/{patient_id}/symptoms/trends - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
-
Query Parameters (opcionais):
period— Granularidade:daily,weeklyoumonthly(padrão:monthly)start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)
-
Exemplo de Chamada:
GET /api/v1/patients/5/symptoms/trends?period=weekly&start_date=2026-01-01T00:00:00Z&end_date=2026-02-19T23:59:59Z -
Resposta de Sucesso:
json { "patient_id": 5, "period_type": "weekly", "total_records": 32, "most_frequent": [ {"symptom": "Fadiga", "count": 15, "avg_intensity": 6.2}, {"symptom": "Dormência", "count": 10, "avg_intensity": 4.8}, {"symptom": "Dor de cabeça", "count": 7, "avg_intensity": 5.1} ], "trends": [ { "period": "2026-W03", "count": 8, "avg_intensity": 5.5, "symptoms": {"Fadiga": 4, "Dormência": 3, "Dor de cabeça": 1} }, { "period": "2026-W04", "count": 6, "avg_intensity": 4.8, "symptoms": {"Fadiga": 3, "Dormência": 2, "Dor de cabeça": 1} } ] } - O campo
most_frequentretorna os top 10 sintomas por frequência. - Cada entrada em
trendsagrupa os sintomas do período com contagem total, intensidade média e breakdown por sintoma.
10.3. Correlação Exercício × Sintomas
Cruza dados de exercício (passos + sessões) com sintomas no mesmo período, permitindo visualizar se atividade física impacta os sintomas.
- Endpoint:
GET /api/v1/patients/{patient_id}/health-correlation - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
-
Query Parameters (opcionais):
period— Granularidade:daily,weeklyoumonthly(padrão:weekly)start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)
-
Exemplo de Chamada:
GET /api/v1/patients/5/health-correlation?period=weekly&start_date=2026-02-01T00:00:00Z&end_date=2026-02-19T23:59:59Z -
Resposta de Sucesso:
json { "patient_id": 5, "period_type": "weekly", "periods": [ { "period": "2026-W06", "total_steps": 42000, "exercise_sessions": 3, "exercise_minutes": 105.0, "sleep_hours": 52.5, "avg_heart_rate": 72, "symptom_count": 4, "avg_symptom_intensity": 5.2 }, { "period": "2026-W07", "total_steps": 28000, "exercise_sessions": 1, "exercise_minutes": 30.0, "sleep_hours": 48.0, "avg_heart_rate": 75, "symptom_count": 7, "avg_symptom_intensity": 6.8 } ] } - Cada período inclui dados de exercício (passos totais, sessões, minutos), sono (horas totais), frequência cardíaca (bpm médio) e sintomas (contagem, intensidade média) lado a lado.
- Campos
sleep_hourseavg_heart_ratesão 0 quando não há dados do tipo no período. (Novo na v1.15) - Ideal para gráficos de correlação no frontend.
Fluxo 11: Export de Dados Clínicos (Novo na v1.13)
Endpoints para exportar dados do paciente em formatos padrão (CSV e PDF). Úteis para compartilhar com outros profissionais ou para backup.
11.1. Exportar Sintomas em CSV
- Endpoint:
GET /api/v1/patients/{patient_id}/export/symptoms - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
-
Query Parameters (opcionais):
start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)
-
Exemplo de Chamada:
GET /api/v1/patients/5/export/symptoms?start_date=2026-01-01T00:00:00Z&end_date=2026-02-19T23:59:59Z -
Resposta: Arquivo CSV com encoding UTF-8 BOM (compatível com Excel).
- Content-Type:
text/csv; charset=utf-8 - Content-Disposition:
attachment; filename=sintomas_paciente_5.csv
- Content-Type:
-
Colunas do CSV:
Data/Hora,Sintoma,Intensidade,Observações -
Nota: Se não houver sintomas no período, retorna um CSV vazio (apenas cabeçalho).
11.2. Relatório Clínico Completo em PDF
Gera um relatório profissional em PDF com todos os dados clínicos do paciente.
- Endpoint:
GET /api/v1/patients/{patient_id}/export/report - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
-
Query Parameters (opcionais):
start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)
-
Exemplo de Chamada:
GET /api/v1/patients/5/export/report?start_date=2026-01-01T00:00:00Z -
Resposta: Arquivo PDF.
- Content-Type:
application/pdf - Content-Disposition:
attachment; filename=relatorio_paciente_5.pdf
- Content-Type:
-
Conteúdo do PDF:
- Cabeçalho com logo/nome Lastreo e período do relatório
- Dados do paciente (nome, email)
- Tabela de sintomas (data, sintoma, intensidade, observações)
- Tabela de avaliações clínicas (data, tipo, dados)
- Tabela de registros de saúde (data, tipo, resumo dos dados)
-
Nota: Se não houver dados no período, o PDF conterá apenas o cabeçalho e os dados do paciente, com mensagens indicando "Nenhum registro encontrado".
Fluxo 12: Alertas Clínicos (Novo na v1.15)
Sistema de alertas para notificar médicos sobre eventos clínicos relevantes. Alertas são gerados automaticamente pelo sistema e os médicos associados ao paciente são notificados.
12.1. Tipos de Alerta
Cada alerta possui um campo source que indica sua origem:
ai_chat— Gerado durante a conversa com o assistente de IA.threshold— Gerado automaticamente com base nos dados clínicos do paciente.manual— Reservado para uso futuro.
E um campo severity com três níveis: baixa, media, alta.
12.2. Listar Alertas do Médico
- Endpoint:
GET /api/v1/doctors/me/alerts - Permissões: Apenas médicos.
- Ordenação: Alertas mais recentes primeiro (
created_atdescendente). -
Query Parameters (todos opcionais):
unread_only—truepara filtrar apenas não-lidos (padrão:false)patient_id— Filtrar alertas de um paciente específico
-
Exemplo de Chamada:
GET /api/v1/doctors/me/alerts?unread_only=true -
Resposta de Sucesso:
json [ { "id": 1, "patient_user_id": 5, "doctor_user_id": null, "severity": "alta", "reason": "Paciente reportou dor intensa (9/10) com dormência bilateral.", "source": "ai_chat", "symptom_record_id": 42, "is_read": false, "created_at": "2026-02-20T14:30:00Z", "read_at": null } ] - Erros:
403— Usuário não é médico.403—patient_idfornecido não está associado ao médico.
12.3. Contagem de Alertas Não-Lidos (Badge)
Ideal para exibir um badge/contador no app.
- Endpoint:
GET /api/v1/doctors/me/alerts/unread-count - Permissões: Apenas médicos.
- Resposta de Sucesso:
json { "unread_count": 3 }
12.4. Marcar Alerta Como Lido
- Endpoint:
PATCH /api/v1/doctors/me/alerts/{alert_id}/read - Permissões: Apenas médicos. O alerta deve pertencer a um paciente associado ao médico.
- Resposta de Sucesso:
json { "id": 1, "patient_user_id": 5, "doctor_user_id": 2, "severity": "alta", "reason": "Paciente reportou dor intensa (9/10).", "source": "ai_chat", "symptom_record_id": 42, "is_read": true, "created_at": "2026-02-20T14:30:00Z", "read_at": "2026-02-20T15:00:00Z" } - O campo
doctor_user_idé preenchido automaticamente com o ID do médico que marcou como lido. - Erros:
403— Usuário não é médico.404— Alerta não encontrado ou não pertence aos pacientes do médico.
12.5. Listar Alertas de um Paciente
- Endpoint:
GET /api/v1/patients/{patient_id}/alerts - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
- Resposta: Lista de alertas ordenados por data (mais recente primeiro).
Fluxo 13: Lembretes de Medicação (Novo na v1.15)
CRUD para gerenciar lembretes de medicação. O paciente recebe lembretes automaticamente nos horários configurados via seus canais ativos (WhatsApp/Telegram).
13.1. Criar Lembrete de Medicação
- Endpoint:
POST /api/v1/patients/{patient_id}/medication-reminders - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
- Corpo da Requisição:
json { "medication_name": "Fingolimode", "dosage": "0.5mg", "frequency": "daily", "reminder_times": ["08:00", "20:00"], "notes": "Tomar com comida" } -
Campos:
medication_name(obrigatório): Nome do medicamento.dosage(opcional): Dosagem.frequency(obrigatório):daily,twice_daily,weekly,weekdays.reminder_times(obrigatório): Lista de horários em formatoHH:MM(00:00 a 23:59). Mínimo 1 horário.notes(opcional): Observações.
-
Resposta de Sucesso (201):
json { "id": 1, "patient_user_id": 5, "medication_name": "Fingolimode", "dosage": "0.5mg", "frequency": "daily", "reminder_times": ["08:00", "20:00"], "is_active": true, "notes": "Tomar com comida", "created_at": "2026-02-20T14:30:00Z" } - Erros:
400— Frequência inválida, horário fora do formato HH:MM, ou lista de horários vazia.403— Sem permissão para o paciente.
13.2. Listar Lembretes de Medicação
- Endpoint:
GET /api/v1/patients/{patient_id}/medication-reminders - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
- Query Parameters (opcionais):
active_only—truepara filtrar apenas ativos (padrão:true)
- Resposta: Lista de lembretes ordenados por data de criação.
13.3. Atualizar Lembrete de Medicação
- Endpoint:
PATCH /api/v1/patients/{patient_id}/medication-reminders/{reminder_id} - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
- Corpo da Requisição (todos os campos opcionais):
json { "medication_name": "Fingolimode 0.5mg", "reminder_times": ["09:00"], "is_active": false } - Útil para desativar um lembrete sem deletá-lo (
"is_active": false).
13.4. Deletar Lembrete de Medicação
- Endpoint:
DELETE /api/v1/patients/{patient_id}/medication-reminders/{reminder_id} - Permissões: Apenas o próprio paciente. Médicos NÃO podem deletar lembretes.
- Resposta:
204 No Content - Erros:
403— Sem permissão (médico tentando deletar, ou outro paciente).404— Lembrete não encontrado.
Fluxo 14: Export FHIR R4 (Novo na v1.15)
Export de dados clínicos em formato FHIR R4 (Fast Healthcare Interoperability Resources). Útil para interoperabilidade com sistemas de saúde e prontuários eletrônicos.
14.1. Exportar Bundle FHIR
- Endpoint:
GET /api/v1/patients/{patient_id}/export/fhir - Permissões: Paciente (próprio ID) ou Médico (paciente associado).
-
Query Parameters (opcionais):
start_date— Data/hora inicial (ISO 8601)end_date— Data/hora final (ISO 8601)
-
Exemplo de Chamada:
GET /api/v1/patients/5/export/fhir?start_date=2026-01-01T00:00:00Z -
Resposta de Sucesso:
json { "resourceType": "Bundle", "type": "collection", "entry": [ { "resource": { "resourceType": "Patient", "id": "5", "name": [{"text": "Maria Silva"}], "telecom": [{"system": "email", "value": "[email protected]"}] } }, { "resource": { "resourceType": "Observation", "id": "symptom-42", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "survey"}]}], "code": {"text": "Fadiga"}, "subject": {"reference": "Patient/5"}, "effectiveDateTime": "2026-02-20T14:30:00Z", "valueInteger": 7 } }, { "resource": { "resourceType": "Observation", "id": "health-reg-10", "status": "final", "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "activity"}]}], "code": {"coding": [{"system": "http://loinc.org", "code": "55423-8", "display": "Number of steps"}]}, "subject": {"reference": "Patient/5"}, "effectiveDateTime": "2026-02-20T08:00:00Z", "valueQuantity": {"value": 5000, "unit": "steps"} } }, { "resource": { "resourceType": "DiagnosticReport", "id": "assessment-3", "status": "final", "code": {"text": "EDSS"}, "subject": {"reference": "Patient/5"}, "effectiveDateTime": "2026-02-15T10:00:00Z" } } ] } -
Recursos incluídos no Bundle:
- Patient — Dados básicos do paciente (nome, email)
- Observation (category: survey) — Sintomas com intensidade
- Observation (category: activity/vital-signs) — Health Registries com códigos LOINC
- DiagnosticReport — Avaliações clínicas (EDSS, Walk Test, etc.)
-
Códigos LOINC mapeados: | Tipo | Código | Display | |------|--------|---------| | STEPS | 55423-8 | Number of steps | | EXERCISE_SESSION | 55411-3 | Exercise duration | | SLEEP | 93832-4 | Sleep duration | | NUTRITION | 9052-2 | Calorie intake | | HYDRATION | 9058-9 | Fluid intake | | HEART_RATE | 8867-4 | Heart rate |
Fluxo 15: Chat In-App (v1.16 — Multimédia na v1.19)
Interface de chat REST integrada ao aplicativo, como alternativa ao WhatsApp/Telegram. O chat in-app utiliza a mesma inteligência artificial e as mesmas ferramentas disponíveis nos outros canais. A diferença é que a resposta é retornada diretamente na resposta HTTP, sem necessidade de plataformas externas.
Características:
* Canal in_app criado automaticamente no primeiro uso — não precisa de vinculação manual
* Mesma IA, mesmas ferramentas, mesmo histórico persistente
* Resposta síncrona via HTTP (sem dependência de WhatsApp/Telegram)
* Proteção contra mensagens simultâneas (lock per-user)
* Suporte multimédia: imagens, documentos (PDF), áudio e vídeo
Gerenciamento de Contexto da Conversa:
A IA mantém o contexto da conversa utilizando uma janela deslizante com as mensagens mais recentes. Conforme a conversa avança, o contexto mais antigo é automaticamente resumido e preservado, garantindo que informações importantes não sejam perdidas mesmo em conversas longas. Isso é totalmente transparente para o usuário — a IA sempre tem acesso ao contexto relevante da conversa.
Ao limpar o histórico do chat (DELETE /api/v1/chat/history), todo o contexto é removido, incluindo os resumos acumulados. A próxima conversa começa do zero, sem nenhuma referência a interações anteriores.
15.1. Enviar Mensagem no Chat
- Endpoint:
POST /api/v1/chat/send - Permissões: Qualquer usuário autenticado (paciente ou médico).
- Rate Limit: 20 requisições por minuto.
- Content-Type:
multipart/form-data -
Campos do formulário:
text(opcional): Mensagem de texto. Máximo 5000 caracteres.file(opcional): Arquivo de mídia (imagem, documento, áudio ou vídeo).local_id(opcional): Identificador temporário da mensagem gerado pelo cliente (máx. 100 caracteres). Ecoado na resposta para correlação. Permite reenvio idempotente — se o mesmolocal_idfor reenviado, o servidor retorna a mensagem original sem reprocessar.- Pelo menos
textoufileé obrigatório. Ambos podem ser enviados juntos.
-
Tipos de arquivo aceitos:
Categoria Formatos Tamanho máximo Imagem JPEG, PNG, WebP, GIF 10 MB Documento PDF 10 MB Áudio MP3, WAV, OGG, WebM, M4A, AAC 25 MB Vídeo MP4, WebM, MOV, AVI, MKV 50 MB -
Otimização automática de imagens: Imagens são automaticamente redimensionadas (máx. 2048px no lado maior), comprimidas para JPEG 85%, e metadados EXIF removidos (privacidade — GPS, câmera, etc.). GIFs são preservados sem alteração.
-
Exemplos de envio:
Texto apenas: ``` POST /api/v1/chat/send Content-Type: multipart/form-data
text=Estou sentindo muita fadiga hoje, intensidade 7. local_id=a1b2c3d4 ```
Imagem + texto: ``` POST /api/v1/chat/send Content-Type: multipart/form-data
text=O que você acha deste exame? file=@exame_ressonancia.jpg local_id=e5f6g7h8 ```
Arquivo apenas (sem texto): ``` POST /api/v1/chat/send Content-Type: multipart/form-data
file=@laudo_medico.pdf ```
-
Resposta de Sucesso (200):
O endpoint retorna imediatamente com o ID da mensagem salva e status
"processing". A resposta da IA é obtida via SSE no endpoint/chat/stream(seção 15.2).Sem mídia:
json { "id": 1050, "local_id": "a1b2c3d4", "status": "processing", "created_at": "2026-02-25T15:30:00Z", "media": null }Com mídia:
json { "id": 1051, "local_id": "e5f6g7h8", "status": "processing", "created_at": "2026-02-25T15:30:01Z", "media": { "id": "a1b2c3d4e5f6", "filename": "exame_ressonancia.jpg", "content_type": "image/jpeg", "category": "image", "size": 245760 } }Reenvio idempotente (mesmo
local_id):json { "id": 1050, "local_id": "a1b2c3d4", "status": "accepted", "created_at": "2026-02-25T15:30:00Z", "media": null } -
Campos da resposta:
id: ID da mensagem do usuário no banco de dados.local_id: O mesmolocal_idenviado pelo cliente (ounullse não foi fornecido).status:"processing"(mensagem nova, IA processando) ou"accepted"(reenvio idempotente, mensagem já existia).created_at: Timestamp da mensagem (ISO 8601).media: Objeto com informações da mídia (ounull). Campos:id,filename,content_type,category,size.
-
Comportamento:
- Na primeira mensagem, o sistema cria automaticamente um canal
in_apppara o usuário. - Se
local_idfor fornecido e já existir no histórico, retorna a mensagem existente comstatus: "accepted"(idempotência). - Se houver arquivo, ele é validado (tipo, tamanho) e otimizado (imagens).
- A mensagem do usuário é salva imediatamente no histórico e o ID é retornado.
- A IA processa a mensagem em background — a resposta é entregue via SSE (seção 15.2).
- Na primeira mensagem, o sistema cria automaticamente um canal
-
Erros:
400— Nenhum texto ou arquivo enviado, ou arquivo vazio.401— Token ausente ou inválido.409 Conflict— Outra mensagem do mesmo usuário está sendo processada. Aguardar 2-3 segundos e retentar.413— Arquivo excede o tamanho máximo permitido.415— Tipo de arquivo não suportado.429— Rate limit excedido (mais de 20 mensagens por minuto).
15.2. Receber Resposta da IA (SSE)
- Endpoint:
GET /api/v1/chat/stream?token=<jwt> - Permissões: Qualquer usuário autenticado.
- Rate Limit: 20 requisições por minuto.
- Autenticação: Via query parameter
token(a API EventSource não suporta headers HTTP customizados). -
Content-Type da resposta:
text/event-stream -
Uso: Após chamar
POST /chat/sende receberstatus: "processing", o cliente conecta a este endpoint para receber a resposta da IA via Server-Sent Events (SSE). -
Exemplo de conexão (JavaScript):
``javascript const eventSource = new EventSource(https://lastreo.com/api/v1/chat/stream?token=${accessToken}` );eventSource.addEventListener('status', (e) => { // Heartbeat — IA ainda processando console.log('Processando...'); });
eventSource.addEventListener('message_complete', (e) => { const data = JSON.parse(e.data); // data = { id: 1052, content: "Resposta da IA...", created_at: "..." } displayMessage(data); eventSource.close(); });
eventSource.addEventListener('error', (e) => { const data = JSON.parse(e.data); console.error(data.detail); eventSource.close(); }); ```
-
Eventos SSE:
Evento Dados Descrição status{"status": "processing"}Heartbeat a cada ~5s — a IA ainda está processando message_complete{"id": int, "content": string, "created_at": string}Resposta final da IA error{"detail": string}Erro no processamento -
Campos do
message_complete:id: ID da mensagem da IA no banco de dados.content: Texto da resposta da IA.created_at: Timestamp da resposta (ISO 8601).
-
Comportamento:
- O endpoint mantém a conexão aberta até a resposta estar pronta (máx. 120s).
- Se o cliente conectar após a resposta já estar pronta, o evento
message_completeé enviado imediatamente. - Após entregar o
message_completeouerror, a conexão é encerrada pelo servidor. - Se o cliente desconectar antes da resposta, a IA continua processando e a resposta é salva no histórico — o cliente pode recuperá-la via
GET /chat/history.
-
Erros via evento SSE:
"Nenhuma mensagem em processamento."— O cliente conectou sem ter enviado uma mensagem via/chat/sendprimeiro."Não foi possível obter resposta da IA."— Falha no serviço de IA."Timeout: a resposta demorou mais que o esperado."— Processamento excedeu 120 segundos.
-
Erros HTTP:
401— Token ausente ou inválido.422— Parâmetrotokennão fornecido.429— Rate limit excedido.
15.4. Mídia no Chat — Modelo de Cache Local
O Lastreo não armazena arquivos de mídia no servidor. Ao enviar um arquivo via /chat/send, o servidor processa o conteúdo (envia à IA para análise), salva apenas os metadados no banco (id, filename, content_type, category, size) e descarta os bytes.
Responsabilidade do frontend:
1. Ao enviar mídia com sucesso, salvar o arquivo localmente no dispositivo, indexado pelo media.id retornado na resposta do /chat/send.
2. Ao exibir o histórico, verificar se o media.id existe no cache local:
* Se existir: exibir o arquivo (thumbnail, player de áudio/vídeo, preview de PDF).
* Se não existir (cache limpo, troca de dispositivo): exibir placeholder — ex: "📎 exame_ressonancia.jpg (imagem, 245 KB) — arquivo disponível apenas no dispositivo original".
3. Mensagens da IA que analisaram mídia incluem o contexto da análise no texto da resposta, então o conteúdo semântico não se perde mesmo sem o arquivo.
Por que este modelo: * Zero consumo de armazenamento no servidor * Conformidade LGPD simplificada (dados sensíveis permanecem no dispositivo do usuário) * Mesmo modelo utilizado por WhatsApp, Telegram e Signal
Não existe endpoint
GET /chat/media/{id}— arquivos não são servidos pelo servidor.
15.5. Obter Histórico do Chat
- Endpoint:
GET /api/v1/chat/history - Permissões: Qualquer usuário autenticado (retorna apenas seu próprio histórico).
- Rate Limit: 60 requisições por minuto.
- Ordenação: Mais recente primeiro (DESC). Ideal para telas de chat — o app carrega as mensagens mais novas e pagina para trás ao fazer scroll para cima.
Query Parameters (todos opcionais):
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
limit |
int | 50 |
Número máximo de mensagens por página (1–200) |
before_id |
int | — | Retorna mensagens com id menor que este valor (cursor recomendado) |
before |
ISO 8601 | — | Retorna mensagens com timestamp anterior a este valor |
Paginação por cursor: Na primeira chamada, não passe nenhum cursor. A resposta inclui
next_cursor_id— passe-o comobefore_idna próxima chamada para carregar a página seguinte (mensagens mais antigas). Repita atéhas_more: false.
-
Exemplo — Primeira página (mais recentes):
GET /api/v1/chat/history?limit=20 -
Exemplo — Página seguinte (mais antigas):
GET /api/v1/chat/history?limit=20&before_id=42 -
Resposta de Sucesso (200):
json { "messages": [ { "id": 50, "role": "assistant", "content": "Analisei a imagem do seu exame. Os resultados parecem normais.", "tool_name": null, "media": null, "timestamp": "2026-02-25T15:30:02Z" }, { "id": 49, "role": "user", "content": "Veja meu exame", "tool_name": null, "media": { "id": "a1b2c3d4e5f6", "filename": "exame.jpg", "content_type": "image/jpeg", "category": "image", "size": 245760 }, "timestamp": "2026-02-25T15:30:00Z" } ], "has_more": true, "total": 120, "next_cursor": "2026-02-25T15:30:00Z", "next_cursor_id": 49 } -
Campos da resposta:
messages: Lista de mensagens (mais recente primeiro).has_more:truese existem páginas mais antigas.total: Contagem total de mensagens do usuário.next_cursor: Timestamp da mensagem mais antiga da página (use comobeforena próxima chamada).nullse não há mais páginas.next_cursor_id: ID da mensagem mais antiga da página (use comobefore_idna próxima chamada — recomendado, mais preciso).nullse não há mais páginas.
-
Campos da mensagem:
id: Identificador único da mensagem.role: Papel do autor —"user"(usuário),"assistant"(IA) ou"tool"(resultado de ferramenta).content: Texto da mensagem. Pararole: "tool", é uma string JSON com o resultado da ferramenta.tool_name: Nome da ferramenta executada (apenas quandorole: "tool",nullnos demais).media: Objeto com informações da mídia anexada (apenas em mensagens do usuário com arquivo,nullnos demais).timestamp: Data/hora da mensagem (ISO 8601).
-
Nota: Se o usuário nunca usou o chat in-app, a resposta será uma lista vazia com
total: 0.
15.6. Limpar Histórico do Chat (LGPD)
- Endpoint:
DELETE /api/v1/chat/history - Permissões: Qualquer usuário autenticado (apaga apenas seu próprio histórico).
- Rate Limit: 5 requisições por hora.
- Resposta:
204 No Content - Comportamento: Remove permanentemente todo o histórico de mensagens do chat in-app do usuário, incluindo todos os arquivos de mídia do servidor. Ação irreversível. Compliance com LGPD (direito ao apagamento).
Fluxo Completo para o Frontend:
1. Ao abrir a tela de chat, carregar a primeira página com GET /api/v1/chat/history?limit=30 (mensagens mais recentes primeiro).
2. Quando o usuário fizer scroll para cima e has_more for true, carregar a próxima página com GET /api/v1/chat/history?limit=30&before_id={next_cursor_id}.
3. Para enviar mensagem:
1. Gerar um local_id único (ex: UUID) para a mensagem.
2. Exibir a mensagem do usuário na UI imediatamente (optimistic rendering).
3. Chamar POST /api/v1/chat/send com text, file (opcional) e local_id.
4. Ao receber a resposta 200, substituir o local_id temporário pelo id real retornado pelo servidor.
5. Imediatamente conectar ao GET /api/v1/chat/stream?token=<jwt> via EventSource.
6. Exibir indicador de "digitando..." enquanto recebe eventos status: processing.
7. Ao receber message_complete, exibir a resposta da IA e fechar a conexão SSE.
8. Se receber error, exibir mensagem de erro e fechar a conexão.
4. Se receber 409 no /chat/send, aguardar 2-3 segundos e retentar automaticamente (outra mensagem está em processamento).
5. Reenvio (retry): Se a rede falhar durante o envio, reenviar com o mesmo local_id. O servidor detecta a duplicata e retorna status: "accepted" sem reprocessar.
6. Fallback: Se a conexão SSE cair antes de receber a resposta, a IA continua processando. A resposta estará disponível via GET /chat/history na próxima consulta.
7. Se media estiver presente na resposta do send, salvar o arquivo localmente no dispositivo indexado pelo media.id (ver seção 15.4 — o servidor não armazena nem serve arquivos de mídia).
Nota sobre canais: O canal in_app é criado automaticamente e não pode ser vinculado manualmente via Fluxo 2. A vinculação manual é apenas para WhatsApp e Telegram.
Fluxo 16: Device Tokens (Push Notifications) (Novo na v1.21)
O App EM pode registrar um token FCM (Firebase Cloud Messaging) para receber notificações push nativas no dispositivo Android ou iOS. Notificações são enviadas automaticamente em dois eventos:
- Alerta clínico gerado — o médico recebe notificação push dos alertas dos seus pacientes.
- Lembrete de medicação — o paciente recebe notificação no horário configurado.
O token deve ser registrado após o login e re-registrado sempre que o FCM SDK gerar um novo token (ex: reinstalação do app, limpeza de dados). Um usuário pode ter múltiplos tokens ativos simultaneamente (um por dispositivo).
16.1 Registrar Token de Dispositivo
POST /api/v1/users/me/device-tokens
Registra ou atualiza um token FCM para o usuário logado. Se o token já existir no banco (ex: outro usuário fez login no mesmo dispositivo), ele é reatribuído ao usuário atual (upsert por token).
Autenticação: Bearer Token obrigatório.
Request Body:
{
"token": "fcm_token_gerado_pelo_sdk",
"platform": "android"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
token |
string |
Sim | Token FCM gerado pelo Firebase SDK |
platform |
string |
Sim | Plataforma: "android" ou "ios" |
Response 201 Created:
{
"id": 42,
"platform": "android",
"created_at": "2026-03-02T12:00:00Z"
}
Guarde o
idretornado — ele é necessário para desregistrar o token no logout.
Erros:
- 401 Unauthorized — Não autenticado.
- 422 Unprocessable Entity — platform inválido (aceita apenas "android" ou "ios").
16.2 Desregistrar Token de Dispositivo
DELETE /api/v1/users/me/device-tokens/{token_id}
Remove o token FCM do banco. Deve ser chamado no logout ou quando o usuário desativar notificações.
Autenticação: Bearer Token obrigatório.
| Parâmetro | Tipo | Descrição |
|---|---|---|
token_id |
integer |
ID do token retornado no registro (campo id) |
Response 204 No Content
Erros:
- 401 Unauthorized — Não autenticado.
- 404 Not Found — Token não encontrado ou pertence a outro usuário.
16.3 Setup Android (Firebase Messaging)
Passo a passo para configurar o Firebase Cloud Messaging no projeto Android.
Pré-requisito: O arquivo google-services.json (fornecido separadamente) deve estar na pasta app/ do projeto.
1. build.gradle (nível do projeto):
plugins {
// ... outros plugins
id("com.google.gms.google-services") version "4.4.4" apply false
}
2. build.gradle (nível do app):
plugins {
id("com.android.application")
id("com.google.gms.google-services") // deve ser o último plugin
}
dependencies {
// Firebase BoM (gerencia versões automaticamente)
implementation(platform("com.google.firebase:firebase-bom:34.10.0"))
implementation("com.google.firebase:firebase-messaging")
}
3. AndroidManifest.xml — registrar o serviço:
<service
android:name=".LastreoFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
4. Implementar o serviço (LastreoFirebaseMessagingService.kt):
class LastreoFirebaseMessagingService : FirebaseMessagingService() {
// Chamado quando o Firebase gera/renova o token
override fun onNewToken(token: String) {
super.onNewToken(token)
// Enviar o novo token ao backend:
// POST /api/v1/users/me/device-tokens { token, platform: "android" }
// Se havia um token anterior, o backend faz upsert automaticamente
}
// Chamado quando uma notificação chega com o app em foreground
override fun onMessageReceived(message: RemoteMessage) {
super.onMessageReceived(message)
// message.data contém os campos enviados pelo backend:
// - "alert_id", "patient_id", "source" (para alertas clínicos)
// - "reminder_id", "type" (para lembretes de medicação)
// Exibir notificação local ou atualizar a UI
}
}
5. Obter o token no login (na Activity/ViewModel):
FirebaseMessaging.getInstance().token.addOnSuccessListener { token ->
// Registrar no backend:
// POST /api/v1/users/me/device-tokens { token: token, platform: "android" }
// Salvar o "id" retornado localmente para usar no logout
}
6. No logout — desregistrar:
// Chamar antes de limpar a sessão:
// DELETE /api/v1/users/me/device-tokens/{id_salvo}
Nota: Em Android 13+ (API 33), é necessário solicitar a permissão
POST_NOTIFICATIONSem runtime antes de exibir notificações. Adicionar<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />no manifest.
Notas de Integração
- Quando registrar: Logo após o login bem-sucedido, obtenha o token via
FirebaseMessaging.getInstance().getToken()e chamePOST /users/me/device-tokens. Salve oidretornado localmente. - Quando re-registrar: Ao receber
onNewTokennoFirebaseMessagingService— o Firebase renova o token periodicamente. Registre o novo token e descarte oidanterior. - No logout: Chame
DELETE /users/me/device-tokens/{id}antes de invalidar o token local para evitar notificações no dispositivo após a saída. - Múltiplos dispositivos: Um usuário pode ter múltiplos tokens simultâneos (um por dispositivo). Cada login em um novo dispositivo adiciona um token distinto.
- Notificações com app fechado: O Firebase entrega notificações automaticamente quando o app está em background/fechado.
onMessageReceivedsó é chamado com o app em foreground.
Fluxo 17: Verificação Profissional de Médicos (Novo na v1.29)
Contexto. Contas criadas com role: "medico" precisam comprovar identidade profissional antes de acessar dados clínicos de pacientes. A verificação tem dois caminhos que o médico pode escolher dependendo do que tem em mãos:
- Tier Ouro (
ouro): assinatura digital em PDF usando certificado e-CPF AR-CFM (ICP-Brasil). Aprovação automática e imediata, força jurídica plena (instrumento público — MP 2.200-2/2001). Indicado para médicos que já possuem o certificado. - Tier Prata (
prata): envio de foto da carteira CRM + selfie. Análise manual pela equipe Lastreo em até 24h. Indicado para médicos que ainda não emitiram certificado digital.
Os dois caminhos liberam o mesmo conjunto de funcionalidades hoje. A diferenciação por verification_tier existe para que recursos futuros que exijam valor jurídico forte (ex.: prescrição digital, laudos assinados) possam exigir tier: "ouro" especificamente.
Estados possíveis no perfil do médico:
| Campo | Valores | Significado |
|---|---|---|
verification_status |
"pending" |
médico ainda não foi verificado |
"approved" |
médico aprovado — pode usar funcionalidades clínicas | |
"rejected" |
última tentativa (tier ouro) falhou; ver last_rejection_reason |
|
null |
usuário não é médico | |
verification_tier |
"ouro" |
aprovado via assinatura AR-CFM |
"prata" |
aprovado via análise manual | |
null |
não aprovado ainda |
Endpoints bloqueados para médicos não-aprovados (403):
Todos os endpoints doctors/me/* (exceto os de verificação abaixo), /patients/search, /patients/{id}, /patients/{id}/assessments (EDSS em particular), listagem/criação/remoção de pacientes, dashboard, alertas, /admin/ai-usage. O erro retornado tem detail com instrução para completar a verificação.
17.A — Tier Ouro (AR-CFM / ICP-Brasil)
Médico assina digitalmente um PDF personalizado com seu certificado e-CPF emitido pela AR-CFM (Autoridade de Registro do Conselho Federal de Medicina), certificadodigital.cfm.org.br. Como a AR-CFM só emite certificados para médicos com CRM ativo, a validação criptográfica da assinatura prova simultaneamente a autenticidade da identidade profissional.
A1. Gerar Termo de Verificação
Endpoint: POST /api/v1/doctors/me/verification/terms
Auth: Bearer token do médico (mesmo com verification_status: "pending")
Rate limit: 5 por hora
Body: nenhum — o termo é gerado usando full_name do perfil do médico (atualize-o antes via PATCH /users/me se necessário).
Resposta (sucesso 200): Content-Type: application/pdf — binário do PDF. Header Content-Disposition: attachment; filename="lastreo-termo-verificacao-{user_id}.pdf". O PDF contém:
- Texto do termo de aceite
- Nome do médico
- Código único de verificação (32 caracteres hex) embutido também nos metadados do PDF (XMP)
user_ide data de emissão
Validade do termo: 24 horas, uso único. Gerar um novo termo invalida automaticamente termos anteriores não-usados deste mesmo médico.
Erros:
- 403 — não é médico
- 422 — full_name está vazio no perfil; atualize via PATCH /users/me
A2. Assinar o PDF com e-CPF AR-CFM
O médico usa qualquer software de assinatura digital compatível com ICP-Brasil (ex.: Assinador Serpro, Adobe Acrobat Reader, Assinador da Valid) para assinar o PDF (padrão PAdES) com seu certificado digital emitido via AR-CFM.
Observação técnica: o certificado precisa conter o OID 2.16.76.1.4.2.2.1 (número de CRM) populado. Certificados e-CPF comuns (emitidos por outras ARs, não a AR-CFM) não contêm esse OID e serão rejeitados.
A3. Submeter o PDF Assinado
Endpoint: POST /api/v1/doctors/me/verification/submit
Auth: Bearer token do médico
Rate limit: 3 por hora
Content-Type: multipart/form-data
Campo: file — o PDF assinado (máximo 10 MB)
Exemplo (curl):
curl -X POST https://lastreo.com/api/v1/doctors/me/verification/submit \
-H "Authorization: Bearer <token>" \
-F "[email protected]"
Resposta (aprovado, 200):
{
"verification_status": "approved",
"verification_tier": "ouro",
"verified_full_name": "BRUNO FERNANDES",
"verified_crm": "987654",
"verified_uf": "SP",
"verified_specialty": "NEUROCIRURGIA",
"rejection_reason": null
}
Dados verificados (nome, CRM, UF, especialidade, CPF) são preenchidos automaticamente no perfil do médico quando estiverem vazios. O médico passa a ter acesso imediato a todos os endpoints clínicos.
Erros (a conta é marcada como rejected em qualquer falha; audit log gravado com cert fingerprint, IP, motivo):
| Código | Quando ocorre | Detail típico |
|---|---|---|
400 |
PDF malformado, sem assinatura, conteúdo alterado após assinatura | "Assinatura inválida: ..." |
413 |
PDF maior que 10 MB | "PDF muito grande (limite 10 MB)." |
422 |
Certificado revogado | "Certificado revogado: ..." |
422 |
Certificado expirado | "Certificado expirado: ..." |
422 |
Cadeia de certificação não conecta à raiz ICP-Brasil | "Cadeia de certificação inválida: ..." |
422 |
Certificado não contém OID de CRM (não é AR-CFM) | "Certificado não contém OID de CRM..." |
422 |
Código do PDF não encontrado / expirado / já usado | "Código de verificação não encontrado no PDF..." |
422 |
Nome do certificado não bate com o nome do perfil | "Nome do certificado ('X') não coincide com o nome do perfil ('Y')..." |
429 |
Excedeu 3 tentativas por hora | "Rate limit exceeded" |
504 |
Verificação de revogação (OCSP/CRL) indisponível | "Tempo esgotado ao validar a assinatura..." |
17.B — Tier Prata (Análise manual)
Para médicos sem certificado digital AR-CFM. A submissão fica em fila e é revisada manualmente pela equipe Lastreo em até 24h. Apenas uma submissão em revisão por médico de cada vez — submeter de novo cancela a anterior automaticamente.
B1. Submeter Verificação Prata
Endpoint: POST /api/v1/doctors/me/verification/basic/submit
Auth: Bearer token do médico
Rate limit: 2 por dia
Content-Type: multipart/form-data
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
crm |
string | sim | Número do CRM (somente dígitos, opcionalmente uma letra ao final). Max 20 chars. |
uf |
string | sim | UF do CRM, sigla 2 letras (ex: SP). |
full_name |
string | sim | Nome completo exatamente como consta na carteira CRM. 2–255 chars. |
cpf |
string | não | CPF (XXX.XXX.XXX-XX ou só dígitos). |
specialty |
string | não | Especialidade. Max 200 chars. |
crm_card_image |
file | sim | Foto da carteira CRM (frente legível). JPEG/PNG/WebP, máx 8 MB. |
selfie_image |
file | sim | Selfie segurando a carteira CRM com o rosto e o documento visíveis. JPEG/PNG/WebP, máx 8 MB. |
As imagens passam por validação (Pillow), são re-codificadas para remover EXIF (privacidade) e armazenadas com acesso restrito.
Resposta (200) — DoctorBasicVerificationOut:
{
"id": 42,
"status": "pending_review",
"crm_declared": "123456",
"uf_declared": "SP",
"full_name_declared": "Dr. Joao Silva",
"cpf_declared": null,
"specialty_declared": "Neurologia",
"reviewer_notes": null,
"reviewed_at": null,
"created_at": "2026-05-19T10:32:11.123456+00:00"
}
Erros:
| Código | Motivo |
|---|---|
403 |
usuário não é médico |
422 |
CRM/UF/CPF em formato inválido, ou imagem não-imagem |
413 |
imagem maior que 8 MB (já tratado dentro do 422 com mensagem explicativa) |
429 |
excedeu 2 submissões por dia |
B2. Consultar Estado da Submissão Prata
Endpoint: GET /api/v1/doctors/me/verification/basic
Auth: Bearer token do médico
Retorna o DoctorBasicVerificationOut da submissão mais recente, ou 404 se nunca submeteu.
status pode ser "pending_review" (aguardando), "approved" (aprovada — o perfil do médico estará com verification_status=approved e verification_tier=prata), ou "rejected" (rejeitada — motivo em reviewer_notes).
17.C — Status agregado
Endpoint: GET /api/v1/doctors/me/verification/status
Auth: Bearer token do médico
Mostra o estado canônico do perfil do médico, independente de qual caminho ele usou:
{
"verification_status": "approved",
"verification_tier": "ouro",
"verified_full_name": "BRUNO FERNANDES",
"verified_crm": "987654",
"verified_uf": "SP",
"verified_specialty": "NEUROCIRURGIA",
"verification_submitted_at": "2026-05-19T10:32:11.123456+00:00",
"verification_approved_at": "2026-05-19T10:32:11.987654+00:00",
"last_rejection_reason": null
}
last_rejection_reason é preenchido apenas quando verification_status: "rejected" (rejeição do tier ouro). Rejeições do tier prata aparecem no estado da submissão (GET /verification/basic), não aqui — pois o perfil do médico permanece em "pending" enquanto não há aprovação.
Resumo para o Frontend
- Ao detectar
verification_status: "pending"ou"rejected", exibir tela de onboarding com dois caminhos: - "Verificação imediata (AR-CFM)" — explica o que é, link paracertificadodigital.cfm.org.br, botão "Gerar termo" que chamaPOST /verification/termse baixa o PDF. - "Verificação por análise" — formulário com CRM, UF, nome, foto da carteira, selfie. Submete viaPOST /verification/basic/submit. - Para tier ouro: após download, orientar o médico a assinar o PDF e fazer upload via
POST /verification/submit. Em caso de erro, mostrardetaile permitir retry (até 3/hora). - Para tier prata: após submissão, mostrar tela "Verificação em análise — aguardar até 24h" e fazer polling de
GET /verification/basicou usar push notification quando aprovado. GET /verification/statuspode ser chamado ao abrir o app para sincronizar estado canônico.verification_tieré informativo no frontend — não precisa diferenciar UI por tier hoje.
Fluxo 18: Verificação Hospedada (Embed) (Guia consolidado — v1.58+)
O fluxo hospedado é a forma recomendada de verificar a identidade de um médico sem que documentos e biometria passem pelo seu backend. Você cria uma sessão no servidor, embeda uma página hospedada pelo Lastreo (iframe), e o médico interage diretamente com o Lastreo. Sob LGPD, você deixa de ser controlador do dado biométrico — o Lastreo é o operador desse dado.
Visão geral (4 passos):
- Server-side: seu backend chama
POST /api/v1/client/verification-sessions(opcionalmente comrequired_tier) e recebe umembed_url. - Frontend: você embeda o
embed_url(via SDK drop-in ou iframe manual). - Médico: completa a verificação no método correspondente ao
required_tierda sessão — bronze (CRM + UF + nome), prata (carteira + selfie) ou ouro (assinatura digital ICP-Brasil) — ou reutiliza a identidade via Lastreo Link, tudo dentro do iframe. - Resultado: você recebe o desfecho por
postMessagee/ou redirect (UI), e confirma server-side viaGET .../verification-sessions/{session_id}e/ou webhooks.
18.1. Criar a sessão (server-side)
POST /api/v1/client/verification-sessions
Autenticação: X-API-KEY: lk_... (recomendado para chamadas server-to-server) ou Bearer JWT de client_admin.
Request body (todos os campos são opcionais):
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
client_user_ref |
string | null |
Identificador do médico/estudante no seu sistema. Volta nos webhooks da sessão (correlação sem lookup reverso) e garante idempotência: re-tentativas da mesma pessoa com o mesmo client_user_ref reusam a mesma identidade — sem cadastro duplicado nem nova cobrança. Recomendado sempre que o usuário puder recomeçar a verificação. |
kind |
string | "physician" |
Tipo de verificação: "physician" (médico, padrão) ou "student" (estudante de medicina). Em sessões student, a página oferece e-mail institucional ou comprovante de matrícula — os campos allow_silver/allow_gold aplicam-se só a médico. Ver 18.6. |
allow_silver |
bool | true |
Permite o caminho "carteira CRM + selfie" (resultado tier prata). |
allow_gold |
bool | true |
Permite o tier ouro na sessão. O ouro é atingido por reuso de identidade já-ouro via Lastreo Link; a assinatura ICP-Brasil em si é feita fora do fluxo hospedado. |
required_tier |
string | null |
Tier da sessão: bronze | prata | ouro (aliases basic/silver/strong/gold aceitos). Dirige o método que o iframe apresenta ao médico: bronze = confirmação rápida (CRM + UF + nome, sem documento); prata = carteira + selfie; ouro = assinatura digital ICP-Brasil (o médico baixa um termo, assina com o certificado AR-CFM e reenvia, dentro do iframe). Também é o piso aceito pelo Lastreo Link: o reuso de identidade só conclui se o tier reusado for ≥ este. Só vale para kind: "physician". Sem valor, o padrão é prata. Devolvido em GET /client/verification-sessions/{id}. |
theme_primary_color |
string | null |
Cor primária do wizard, formato #RRGGBB. |
hide_branding |
bool | false |
Oculta o branding "Verificado por Lastreo" (white-label). |
completion_redirect_url |
string | null |
Se informado, ao concluir o iframe redireciona o navegador para esta URL com ?session_id=&status=&tier=. |
allowed_origins |
array | null |
Origens permitidas para embedar o iframe. |
expires_in_minutes |
int | 60 |
Validade do link, entre 10 e 240. |
Response 201:
{
"id": 11,
"session_id": "vs_Wx6drlhZuwS1UK8vXYhqt2AL",
"embed_url": "https://lastreo.com/dashboard/v/?t=PQPqkZtz...E4gi",
"expires_at": "2026-05-28T23:15:30Z",
"status": "pending"
}
⚠️ Use o
embed_urlVERBATIM. Ele já contém um token de uso único na query (?t=...), entregue uma única vez nesta resposta. É esse token que autoriza a sessão.
session_id(vs_...) NÃO é o token. É um identificador público, usado apenas para (a) consultar o resultado server-side (GET .../verification-sessions/{session_id}) e (b) correlacionar webhooks. Nunca monte a URL do iframe a partir dosession_id— isso resulta em "Sessão inválida ou expirada", porque osession_idnão autoriza nada. Igualmente,id(inteiro) é uma PK interna — não use em URLs.
18.2. Renderizar no frontend
Opção A — SDK drop-in (recomendado). Sirva o script e passe o embed_url:
<script src="https://lastreo.com/dashboard/sdk/lastreo.js"></script>
<div id="lastreo-verify"></div>
<script>
// embedUrl vem do POST /client/verification-sessions (server-side).
const lastreo = Lastreo({ embedUrl: EMBED_URL_DO_SEU_BACKEND });
// (A1) Inline, dentro de um container seu:
lastreo.mount('#lastreo-verify', {
onComplete: ({ status, session_id, tier }) => { /* ... */ },
theme: { primaryColor: '#0f766e' }, // opcional
});
// (A2) Modal full-screen com overlay + botão de fechar já incluídos:
const modal = lastreo.openModal({
onComplete: ({ status, session_id, tier }) => { /* ... */ },
onClose: () => { /* usuário fechou */ },
});
// modal.close(); // fechar programaticamente, se precisar
</script>
Opção B — iframe manual (se você não quiser o SDK):
<iframe src="EMBED_URL_DO_SEU_BACKEND"
allow="camera; clipboard-write"
style="border:0;width:480px;height:720px"></iframe>
⚠️ Sobre o botão de fechar do modal: o iframe renderiza apenas o conteúdo do wizard. Ele não pode fechar a si mesmo — fechar/esconder o modal é responsabilidade de quem o renderiza (o seu app). Se você monta o seu próprio modal/overlay (ou usa
mount()num container seu), o botão de fechar é seu. Para ganhar overlay + botão "×" + auto-fechamento ao concluir sem implementar nada, useopenModal()do SDK. Se preferir o seu próprio modal, adicione seu botão de fechar e, opcionalmente, escute opostMessagede conclusão (abaixo) para fechá-lo automaticamente.
A câmera (allow="camera") é necessária para a captura da selfie. O iframe precisa ser servido sobre HTTPS.
18.3. Receber o resultado (UI)
Ao concluir (sucesso ou falha), o iframe emite um postMessage para a janela-pai:
window.addEventListener('message', (e) => {
if (e.data?.type === 'lastreo.verify.complete') {
const { status, session_id, tier } = e.data;
// status: "completed" (verificado) | "failed" (rejeitado)
// tier: "bronze" | "prata" | "ouro" | null
}
});
(O SDK já faz esse addEventListener por você e entrega via onComplete.)
Se você passou completion_redirect_url ao criar a sessão, o iframe também redireciona o navegador para essa URL ao concluir, acrescentando ?session_id=&status=&tier=.
⚠️
postMessagee redirect são sinais de UI (convenientes para atualizar a tela). A fonte de verdade é server-side — confirme sempre via 18.4 antes de liberar acesso/conceder privilégios.
18.4. Confirmar server-side (fonte de verdade)
GET /api/v1/client/verification-sessions/{session_id}
Use o session_id (vs_...) da criação. Retorna o estado canônico da sessão:
{
"id": 11,
"session_id": "vs_Wx6drlhZuwS1UK8vXYhqt2AL",
"client_user_ref": "seu-sistema:medico:789",
"status": "completed",
"final_tier": "prata",
"doctor_user_id": 42,
"declared_full_name": "...",
"declared_crm": "123456",
"declared_uf": "SE",
"expires_at": "2026-05-28T23:15:30Z",
"started_at": "2026-05-28T22:46:10Z",
"completed_at": "2026-05-28T22:49:02Z",
"created_at": "2026-05-28T22:45:30Z"
}
Valores de status: pending (criada, médico não começou) · in_progress (preenchendo) · processing (em análise) · completed (verificado, ver final_tier) · failed (rejeitado) · expired (passou de expires_at) · cancelled.
final_tier: bronze · prata (caminho carteira+selfie) · ouro (assinatura ICP-Brasil) · null enquanto não concluído.
Alternativamente (recomendado para não fazer polling), configure webhooks e escute verification.submitted / verification.approved / verification.rejected — quando a sessão tem client_user_ref, esses eventos carregam session_id + client_user_ref no data (ver changelog v1.61).
18.5. Expiração e novas tentativas
O link vale expires_in_minutes (10–240, default 60). Após expirar, o wizard exibe "Sessão inválida ou expirada" — basta criar uma nova sessão e embedar o novo embed_url. O token é de uso único por sessão; não o reaproveite entre médicos.
Resumo para o Frontend
- Seu backend cria a sessão (
POST /client/verification-sessions) e entrega oembed_urlao seu frontend. - Embede o
embed_urlverbatim — viaLastreo({embedUrl}).mount(...)/.openModal(...)(recomendado) ou iframe manual comallow="camera". - Nunca construa a URL do iframe a partir do
session_idou doid. - Se você renderiza o próprio modal, o botão de fechar é seu (ou use
openModal(), que já o inclui). - Atualize a UI com o
postMessagelastreo.verify.complete, mas confirme o resultado server-side (GET .../verification-sessions/{session_id}ou webhooks) antes de conceder acesso.
18.6. Verificação de estudante de medicina (kind: "student")
O Lastreo verifica profissionais e estudantes de medicina. Para verificar um
estudante, crie a sessão (18.1) passando kind: "student" — todo o resto da
integração é idêntico ao fluxo de médico (embed → postMessage/redirect →
confirmação server-side / webhook).
A diferença está dentro da página hospedada (conduzida pelo Lastreo): em vez de
carteira CRM + selfie, o estudante escolhe um de dois caminhos — e-mail
institucional (recebe um código; quando a instituição é uma escola médica
reconhecida, aprova na hora) ou comprovante de matrícula (aceita PDF ou
imagem). No caminho do comprovante, selfie e documento com foto são opcionais —
quando enviados e a identidade é confirmada, o resultado sobe para verified_strong.
Você não precisa saber qual caminho foi usado: o desfecho chega igual.
Tier do resultado (tier no postMessage, final_tier no GET da sessão, tier no webhook):
tier |
Significado |
|---|---|
verified |
Estudante de medicina verificado (matrícula em Medicina confirmada). |
verified_strong |
Verificado + identidade vinculada por biometria (selfie + documento com foto). |
null |
Submissão recebida, aguardando revisão — você é avisado por webhook ao concluir. |
No
GET /api/v1/client/verification-sessions/{id}o tier do estudante vem emfinal_tier(verified/verified_strong) e o tipo da sessão emkind— não precisa consultar/client/studentssó para saber o resultado.
Criar a sessão:
curl -X POST https://lastreo.com/api/v1/client/verification-sessions \
-H "X-API-KEY: lk_..." -H "Content-Type: application/json" \
-d '{"kind": "student", "client_user_ref": "seu-sistema:aluno:123"}'
# -> { "session_id": "vs_...", "embed_url": "https://.../dashboard/v/?t=...", "status": "pending", ... }
Webhooks: verification.approved / verification.rejected disparam também para
estudantes, com kind: "student" no data (e session_id / client_user_ref
quando a sessão tem o ref — igual ao médico, ver v1.61). Exemplo de data:
{"user_id": 51, "kind": "student", "tier": "verified", "session_id": "vs_ab12cd34", "client_user_ref": "seu-sistema:aluno:123"}.
Listar os estudantes do tenant: GET /api/v1/client/students (API key ou Bearer)
— retorna os estudantes verificados via suas sessões: id, full_name,
verified_full_name, email, verification_status, student_tier,
verification_approved_at.
Ciclo de vida — o estudante vira médico automaticamente. Quando ele obtém o CRM e se verifica como médico (qualquer tier), a mesma identidade Lastreo deixa de ser estudante e passa a médico: some de
GET /api/v1/client/studentse aparece emGET /api/v1/client/doctorscom tierbronze/prata/ouro. Você não precisa religar nada — capturar o estudante hoje é onboardar o médico de amanhã sem atrito.
5. Tratamento de Erros
A API utiliza códigos de status HTTP padrão para indicar o sucesso ou a falha de uma requisição. Em caso de erro (status 4xx ou 5xx), o corpo da resposta conterá um objeto JSON com a descrição do problema.
- Formato do Erro:
json { "detail": "A mensagem de erro específica aparecerá aqui." } - Exemplos Comuns:
401 Unauthorized: Token de acesso ausente, inválido ou expirado. Também retornado em caso de login com credenciais incorretas.403 Forbidden: O usuário não tem permissão para realizar a ação.404 Not Found: O recurso solicitado (ex: um paciente) não foi encontrado.400 Bad Request: A requisição está malformada ou os dados são inválidos (ex: falha na validação de um schema).409 Conflict: Operação em conflito com o estado atual do recurso (ex: outra mensagem do chat in-app está sendo processada — retentar em 2-3s).429 Too Many Requests: O limite de requisições foi excedido (ex: muitas tentativas de login em pouco tempo).502 Bad Gateway: O serviço de IA não está disponível ou não retornou resposta. Tentar novamente.500 Internal Server Error: Erro inesperado no servidor.
6. Referência Rápida de Endpoints
| Método | Endpoint | Descrição | Auth |
|---|---|---|---|
POST |
/auth/register |
Registro de usuário | - |
POST |
/auth/login |
Login (retorna tokens) | - |
POST |
/auth/refresh |
Renovar tokens | - |
POST |
/auth/forgot-password |
Solicitar reset de senha | - |
POST |
/auth/reset-password |
Redefinir senha com código | - |
POST |
/auth/change-password |
Alterar senha (autenticado) | Bearer |
POST |
/auth/send-verification |
Solicitar código de verificação de email | - |
POST |
/auth/verify-email |
Verificar email com código | - |
GET |
/api/v1/users/me |
Perfil do usuário logado | Bearer |
GET |
/api/v1/patients/{id} |
Perfil de um paciente | Bearer |
GET |
/api/v1/patients/search?query= |
Buscar pacientes (médico) | Bearer |
GET |
/api/v1/patients/{id}/symptoms |
Sintomas do paciente | Bearer |
GET |
/api/v1/patients/{id}/assessments |
Avaliações clínicas | Bearer |
POST |
/api/v1/patients/{id}/assessments |
Criar avaliação clínica | Bearer |
GET |
/api/v1/patients/{id}/health-registries |
Listar registros de saúde | Bearer |
POST |
/api/v1/patients/{id}/health-registries |
Criar/upsert registro de saúde | Bearer |
DELETE |
/api/v1/patients/{id}/health-registries/{rid} |
Remover registro individual | Bearer |
DELETE |
/api/v1/patients/{id}/health-registries |
Remover todos os registros (LGPD) | Bearer |
POST |
/api/v1/patients/{id}/health-registries/batch |
Criar/upsert registros em lote | Bearer |
GET |
/api/v1/patients/{id}/health-registries/summary |
Resumo agregado | Bearer |
GET |
/api/v1/channels/me |
Listar canais vinculados | Bearer |
POST |
/api/v1/channels/link-request |
Solicitar vinculação | Bearer |
POST |
/api/v1/channels/link-confirm |
Confirmar vinculação | Bearer |
DELETE |
/api/v1/channels/{id} |
Desvincular canal | Bearer |
POST |
/api/v1/users/claim-request |
Solicitar reivindicação | Bearer |
POST |
/api/v1/users/claim-confirm |
Confirmar reivindicação | Bearer |
POST |
/api/v1/doctors/me/patients |
Vincular paciente existente | Bearer |
POST |
/api/v1/doctors/me/patients/provisional |
Criar paciente provisório | Bearer |
POST |
/api/v1/doctors/me/patients/{id}/merge |
Mesclar provisório em conta real | Bearer |
DELETE |
/api/v1/doctors/me/patients/{id} |
Remover paciente da lista do médico | Bearer |
GET |
/api/v1/doctors/me/patients |
Listar pacientes do médico | Bearer |
POST |
/api/v1/doctors/me/verification/terms |
Gerar PDF de termo de verificação profissional (tier ouro) | Bearer |
POST |
/api/v1/doctors/me/verification/submit |
Submeter PDF assinado com e-CPF AR-CFM (tier ouro) | Bearer |
POST |
/api/v1/doctors/me/verification/basic/submit |
Submeter foto CRM + selfie para análise manual (tier prata) | Bearer |
GET |
/api/v1/doctors/me/verification/basic |
Estado da submissão prata mais recente | Bearer |
GET |
/api/v1/doctors/me/verification/status |
Consultar estado canônico da verificação profissional | Bearer |
GET |
/api/v1/doctors/me/dashboard |
Dashboard do médico | Bearer |
GET |
/api/v1/patients/{id}/symptoms/trends |
Tendências de sintomas | Bearer |
GET |
/api/v1/patients/{id}/health-correlation |
Correlação exercício × sintomas | Bearer |
GET |
/api/v1/patients/{id}/export/symptoms |
Exportar sintomas em CSV | Bearer |
GET |
/api/v1/patients/{id}/export/report |
Relatório clínico em PDF | Bearer |
GET |
/api/v1/patients/{id}/export/fhir |
Export FHIR R4 Bundle | Bearer |
GET |
/api/v1/doctors/me/alerts |
Alertas dos pacientes do médico | Bearer |
GET |
/api/v1/doctors/me/alerts/unread-count |
Contagem de alertas não-lidos | Bearer |
PATCH |
/api/v1/doctors/me/alerts/{id}/read |
Marcar alerta como lido | Bearer |
GET |
/api/v1/patients/{id}/alerts |
Alertas de um paciente | Bearer |
POST |
/api/v1/patients/{id}/medication-reminders |
Criar lembrete de medicação | Bearer |
GET |
/api/v1/patients/{id}/medication-reminders |
Listar lembretes de medicação | Bearer |
PATCH |
/api/v1/patients/{id}/medication-reminders/{rid} |
Atualizar lembrete | Bearer |
DELETE |
/api/v1/patients/{id}/medication-reminders/{rid} |
Deletar lembrete | Bearer |
POST |
/api/v1/chat/send |
Enviar mensagem no chat (multipart: text + file + local_id) | Bearer |
GET |
/api/v1/chat/stream?token=<jwt> |
Receber resposta da IA via SSE | Query param |
| — | — | Mídia: cache local no dispositivo (ver seção 15.4) | — |
GET |
/api/v1/chat/history |
Histórico do chat in-app | Bearer |
DELETE |
/api/v1/chat/history |
Limpar histórico do chat (LGPD) | Bearer |
POST |
/api/v1/users/me/device-tokens |
Registrar token FCM para push notifications | Bearer |
DELETE |
/api/v1/users/me/device-tokens/{id} |
Desregistrar token FCM (logout / troca de device) | Bearer |
6.1 Endpoints B2B de Integração (/client/*)
Superfície para o backend do seu sistema integrar com o Lastreo (verificação de identidade médica, billing, uso). Os endpoints de integração aceitam X-API-KEY: lk_... (recomendado para server-to-server) ou Bearer JWT de client_admin. Os de gerenciamento de conta são exclusivos do dashboard (Bearer JWT) — ver changelog v1.62.
| Método | Endpoint | Descrição | Auth |
|---|---|---|---|
POST |
/api/v1/client/verification-sessions |
Criar sessão de verificação hospedada (embed) | API key ou Bearer |
GET |
/api/v1/client/verification-sessions/{session_id} |
Estado canônico de uma sessão de verificação | API key ou Bearer |
GET |
/api/v1/client/doctors |
Listar médicos verificados que atendem o tenant | API key ou Bearer |
POST |
/api/v1/client/doctors |
Cadastrar/verificar médico (bronze inline) | API key ou Bearer |
GET |
/api/v1/client/students |
Listar estudantes de medicina verificados do tenant | API key ou Bearer |
POST |
/api/v1/client/doctors/{doctor_id}/verify-tier |
Solicitar tier de verificação para um médico | API key ou Bearer |
GET |
/api/v1/client/verifications/usage?days=N |
Uso de verificações por tier + billing estimado (BRL) | API key ou Bearer |
GET |
/api/v1/client/usage |
Agregação de uso de IA do tenant | API key ou Bearer |
GET |
/api/v1/client/usage/by-patient?days=N&limit=M |
Ranking de pacientes por consumo de IA | API key ou Bearer |
GET |
/api/v1/client/compliance/snapshot |
Snapshot de compliance/verificações do tenant | API key ou Bearer |
GET |
/api/v1/client/me |
Dados do tenant | Bearer (dashboard) |
POST |
/api/v1/client/api-keys |
Emitir API key (mostrada uma única vez) | Bearer (dashboard) |
POST |
/api/v1/client/api-keys/{id}/revoke |
Revogar API key | Bearer (dashboard) |
GET POST |
/api/v1/client/webhooks |
Listar / criar webhook (HMAC + retry) | Bearer (dashboard) |
PATCH DELETE |
/api/v1/client/webhooks/{id} |
Atualizar / remover webhook | Bearer (dashboard) |
GET |
/api/v1/client/webhooks/{id}/deliveries |
Histórico de entregas do webhook | Bearer (dashboard) |
POST |
/api/v1/client/webhooks/{id}/test |
Disparar evento de teste | Bearer (dashboard) |
GET PUT DELETE |
/api/v1/client/integrations/ai |
Config BYOK de IA do tenant | Bearer (dashboard) |
GET PUT DELETE |
/api/v1/client/integrations/whatsapp |
Config BYOK de WhatsApp do tenant | Bearer (dashboard) |
GET PUT DELETE |
/api/v1/client/integrations/telegram |
Config BYOK de Telegram do tenant | Bearer (dashboard) |
GET |
/api/v1/client/push-configs |
Catálogo de eventos + config de push por evento | Bearer (dashboard) |
PUT DELETE |
/api/v1/client/push-configs/{event_type} |
Configurar / resetar push de um evento | Bearer (dashboard) |
GET |
/api/v1/client/devices |
Device tokens FCM dos usuários do tenant | Bearer (dashboard) |
POST |
/api/v1/client/push-test |
Enviar push de teste para um device | Bearer (dashboard) |
GET |
/api/v1/client/errors |
Erros do tenant (filtrados) | Bearer (dashboard) |