API pública · versão 1.1.0
Desenvolvedores
A API do Odonto Invest fala em nome de UMA clínica: a chave decide a clínica e os escopos decidem o que ela lê ou faz. O contrato completo, gerado do mesmo código que responde às chamadas, está em /api/v1/public/openapi.json (OpenAPI 3.1). Não há SDK: qualquer cliente HTTP serve.
Autenticação
A clínica cria a chave em Configurações › Integrações › API pública (quem tem permissão de configurar), escolhe os escopos e, se quiser, a validade. A chave aparece uma vez; o banco guarda só o SHA-256. Toda chamada leva o cabeçalho:
Authorization: Bearer oi_live_…
Base: https://odontoinvest.com.br/api/v1/public. Datas da agenda no fuso da clínica (AAAA-MM-DD); instantes das rotas de leitura em UTC (ISO 8601). O que a chave faz fica na trilha de auditoria da clínica como “Integração · nome da chave”.
Escopos
| Escopo | O que abre | Quem pode ter |
|---|---|---|
investcrm | Conexão InvestCRM. Chave da ligação pelo card "Odonto Invest": vale por todos os escopos. | só a conexão com o InvestCRM |
clinica:read | Dados da clínica. Endereço, horários, profissionais, tipos de atendimento, salas e a política do assistente. | qualquer chave |
pacientes:lookup | Consultar paciente. Saber se um telefone tem cadastro e confirmar a identidade (CPF ou nascimento). | qualquer chave |
pacientes:cadastrar | Cadastro rápido. Cadastrar paciente novo com nome, celular e data de nascimento. | qualquer chave |
pacientes:sync | Sincronizar pacientes. Importação e atualização da base de pacientes no CRM (só a conexão). | só a conexão com o InvestCRM |
prontuario:read_ia | Resumo do paciente. Consultas e plano de tratamento do paciente identificado, com trilha (só a conexão). | só a conexão com o InvestCRM |
agenda:read | Ver horários. Horários livres dos profissionais. | qualquer chave |
agenda:write | Marcar e alterar. Marcar, confirmar, desmarcar e remarcar consultas do paciente identificado. | qualquer chave |
cobranca:read | Pendências financeiras. Títulos em aberto do paciente identificado, com link de pagamento e PIX do gateway quando houver. | qualquer chave |
orcamento:aceitar | Aceite de orçamento. Registrar o aceite remoto de um orçamento apresentado do paciente identificado. | qualquer chave |
consultas:read | Ler consultas. Lista de consultas de um período (até 31 dias) e uma consulta por id, sem observações nem texto livre. | qualquer chave |
pacientes:read | Ler paciente. Paciente por id: nome de exibição, prontuário e situação. Sem contato nem dado clínico; cada leitura vai para a trilha. | qualquer chave |
titulos:read | Ler títulos. Títulos a receber por vencimento (até 93 dias), com valores e situação, sem descrição. | qualquer chave |
eventos:read | Feed de eventos. Os mesmos eventos assináveis dos webhooks de saída, lidos por cursor (envelope só com ids, datas e valores). | qualquer chave |
Rotas
Leitura (v1)
| Rota | Escopo | O que faz |
|---|---|---|
| GET /appointments | consultas:read | Consultas de um período (até 31 dias, no fuso da clínica), por início, com cursor; sem observações. |
| GET /appointments/{id} | consultas:read | Uma consulta por id. |
| GET /professionals | clinica:read | Profissionais da clínica (ativos e não), com CRO e especialidade principal. |
| GET /appointment-types | clinica:read | Tipos de atendimento, com a duração padrão. |
| GET /rooms | clinica:read | Salas da clínica. |
| GET /patients/{id} | pacientes:read | Paciente mínimo por id (nome de exibição, prontuário e situação); cada leitura vai para a trilha. |
| GET /receivables | titulos:read | Títulos a receber por vencimento (até 93 dias), com cursor; sem descrição. |
| GET /events | eventos:read | Feed dos eventos assináveis (os mesmos dos webhooks de saída), por cursor do último evento lido. |
A.5
| Rota | Escopo | O que faz |
|---|---|---|
| GET /patients/lookup | pacientes:lookup | O telefone tem cadastro? Com a identidade confirmada, o paciente e a próxima consulta. |
| POST /patients/identify | pacientes:lookup | Confirma a identidade do paciente da conversa (CPF ou data de nascimento). |
| POST /patients/register | pacientes:cadastrar | Cadastro rápido de paciente novo (nome, celular da conversa e nascimento). |
| GET /availability | agenda:read | Horários livres num período (até 14 dias), por profissional ou de todos. |
| POST /appointments | agenda:write | Marca para o paciente identificado (origem ia, status agendado). |
| POST /appointments/{id}/confirm | agenda:write | Confirma a presença numa consulta do paciente identificado. |
| POST /appointments/{id}/cancel | agenda:write | Desmarca uma consulta do paciente identificado. |
| POST /appointments/{id}/reschedule | agenda:write | Remarca uma consulta do paciente identificado (mesmo profissional). |
| GET /clinic | clinica:read | Endereço, horários, profissionais, especialidades e a política do assistente. |
| GET /billing/{patient_id}/open | cobranca:read | Títulos em aberto do paciente identificado, com link de pagamento e PIX do gateway (F3-16). |
| POST /quotes/{id}/accept | orcamento:aceitar | Aceite remoto de orçamento apresentado do paciente identificado (F3-16). |
Ferramentas do agente
| Rota | Escopo | O que faz |
|---|---|---|
| POST /ia/identificar | pacientes:lookup | Ferramenta identificar_paciente. |
| GET /ia/clinica | clinica:read | Ferramenta dados_da_clinica (inclui a política do assistente). |
| GET /ia/horarios | agenda:read | Ferramenta horarios_livres. |
| GET /ia/paciente | prontuario:read_ia | Ferramenta resumo_do_paciente (consultas e plano de tratamento; auditada). |
| POST /ia/pacientes | pacientes:cadastrar | Ferramenta cadastrar_paciente (cadastro rápido de paciente novo). |
| POST /ia/consultas | agenda:write | Ferramenta marcar_consulta. |
| POST /ia/consultas/{id}/confirmar | agenda:write | Ferramenta confirmar_consulta. |
| POST /ia/consultas/{id}/desmarcar | agenda:write | Ferramenta desmarcar_consulta. |
| POST /ia/consultas/{id}/remarcar | agenda:write | Ferramenta remarcar_consulta. |
Sincronização
| Rota | Escopo | O que faz |
|---|---|---|
| GET /patients | pacientes:sync | Importação e delta de pacientes para o CRM (cursor, updated_since). |
| POST /investcrm/revoke | investcrm | O InvestCRM desconectou: a chave e a ligação caem juntas. |
Listas com cursor: mande de volta o next_cursor recebido em cursor, com os mesmos filtros, até has_more ser falso. Consultas: até 31 dias por pedido; títulos: até 93 dias de vencimento; páginas de até 500 itens (padrão 100). O paciente por id é mínimo (nome de exibição, prontuário e situação): nunca contato, documento, anamnese, evolução ou odontograma, e cada leitura vai para a trilha.
Idempotência nos POST
Mande Idempotency-Key (1 a 255 caracteres visíveis, um valor novo por operação) para repetir um POST com segurança: a mesma chave com o mesmo pedido devolve a MESMA resposta por 24 horas, com Idempotent-Replayed: true. A mesma chave com outro pedido responde 422; um pedido igual ainda em andamento, 409. Erro 5xx não fica guardado: pode tentar de novo.
Erros
Sempre {"error": {"code": "...", "message": "..."}}, com a mensagem em português pronta para mostrar.
| HTTP | code | Quando |
|---|---|---|
| 400 | dados, cursor_invalido, idempotencia | parâmetro, corpo, cursor ou Idempotency-Key inválido |
| 401 | chave_invalida | chave ausente, fora do formato, desconhecida, revogada ou vencida |
| 403 | sem_escopo, politica, identificacao | sem o escopo, política do assistente desligada ou falta identificar o paciente |
| 404 | nao_encontrado | o id não existe na clínica da chave |
| 409 | conflito, ja_cadastrado, idempotencia_em_andamento | horário ocupado, telefone já cadastrado ou pedido repetido ainda em andamento |
| 410 | cursor_expirado | o evento do cursor passou da retenção: recomece com since |
| 422 | recusado, inadimplencia, idempotencia_conflito | regra da clínica recusou ou a mesma Idempotency-Key veio com outro pedido |
| 429 | limite | passou do limite; respeite o Retry-After |
| 503 | indisponivel | API desligada no servidor ou banco sem resposta: tente de novo |
Limites
- 120 chamadas por minuto por chave (janela deslizante). Toda resposta liberada leva
X-RateLimit-LimiteX-RateLimit-Remaining; a recusada,Retry-After. - Antes de olhar a chave, um teto por IP; chave errada repetida bloqueia o IP por alguns minutos.
- Até 10 chaves ativas por clínica.
Feed de eventos e webhooks
Os mesmos eventos podem ser recebidos de dois jeitos. No feed (GET /events, escopo eventos:read) a integração busca: a primeira leitura usa since (padrão: últimas 24 h; até 30 dias) e as seguintes, after com o next_after recebido. Eventos com menos de 10 segundos entram na leitura seguinte. Nos webhooks de saída (Configurações › Integrações › Webhooks) o Odonto Invest entrega cada evento num endereço https da integração, com nova tentativa e assinatura. O envelope é o mesmo nos dois: só ids, datas, números e valores — nenhum nome, telefone ou dado clínico.
Cada entrega de webhook leva estes cabeçalhos:
X-OdontoInvest-TimestampX-OdontoInvest-SignatureX-OdontoInvest-EventX-OdontoInvest-DeliveryX-OdontoInvest-Attempt
A assinatura é v1=<HMAC-SHA256(segredo, "<timestamp>.<corpo bruto>")>; na troca do segredo, vêm duas separadas por vírgula. Recuse carimbos com mais de 5 minutos de diferença. Exemplo em Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto'
// corpo = texto BRUTO da requisição (antes de JSON.parse)
function assinaturaValida(segredo, cabecalhos, corpo) {
const ts = cabecalhos['x-odontoinvest-timestamp']
const recebidas = String(cabecalhos['x-odontoinvest-signature'] ?? '').split(',')
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false // anti-replay: 5 minutos
const esperada = 'v1=' + createHmac('sha256', segredo).update(ts + '.' + corpo).digest('hex')
return recebidas.some((a) => a.trim().length === esperada.length &&
timingSafeEqual(Buffer.from(a.trim()), Buffer.from(esperada)))
}Política de versão
A v1 só recebe acréscimo: rota nova, campo novo na resposta e parâmetro opcional novo. Nada é removido nem muda de significado — ignore os campos que você não conhece. Uma mudança incompatível vira /api/v2, com aviso antes.
Histórico de mudanças
API pública: feed de eventos, calendário assinável e documentação completa
A API v1 ganhou o feed de eventos com cursor, o calendário iCal do membro e a documentação OpenAPI na página de desenvolvedores. Só acréscimos: nada mudou de significado.
API pública v1 e webhooks de saída
Chaves de integração por clínica, com escopos, e webhooks assinados para sistemas externos. Tudo desligado até a clínica criar a chave.
