Pular para o conteúdo

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

Escopos da API pública
EscopoO que abreQuem pode ter
investcrmConexão InvestCRM. Chave da ligação pelo card "Odonto Invest": vale por todos os escopos.só a conexão com o InvestCRM
clinica:readDados da clínica. Endereço, horários, profissionais, tipos de atendimento, salas e a política do assistente.qualquer chave
pacientes:lookupConsultar paciente. Saber se um telefone tem cadastro e confirmar a identidade (CPF ou nascimento).qualquer chave
pacientes:cadastrarCadastro rápido. Cadastrar paciente novo com nome, celular e data de nascimento.qualquer chave
pacientes:syncSincronizar 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_iaResumo do paciente. Consultas e plano de tratamento do paciente identificado, com trilha (só a conexão).só a conexão com o InvestCRM
agenda:readVer horários. Horários livres dos profissionais.qualquer chave
agenda:writeMarcar e alterar. Marcar, confirmar, desmarcar e remarcar consultas do paciente identificado.qualquer chave
cobranca:readPendências financeiras. Títulos em aberto do paciente identificado, com link de pagamento e PIX do gateway quando houver.qualquer chave
orcamento:aceitarAceite de orçamento. Registrar o aceite remoto de um orçamento apresentado do paciente identificado.qualquer chave
consultas:readLer 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:readLer 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:readLer títulos. Títulos a receber por vencimento (até 93 dias), com valores e situação, sem descrição.qualquer chave
eventos:readFeed 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)

Rotas do grupo Leitura (v1)
RotaEscopoO que faz
GET /appointmentsconsultas:readConsultas de um período (até 31 dias, no fuso da clínica), por início, com cursor; sem observações.
GET /appointments/{id}consultas:readUma consulta por id.
GET /professionalsclinica:readProfissionais da clínica (ativos e não), com CRO e especialidade principal.
GET /appointment-typesclinica:readTipos de atendimento, com a duração padrão.
GET /roomsclinica:readSalas da clínica.
GET /patients/{id}pacientes:readPaciente mínimo por id (nome de exibição, prontuário e situação); cada leitura vai para a trilha.
GET /receivablestitulos:readTítulos a receber por vencimento (até 93 dias), com cursor; sem descrição.
GET /eventseventos:readFeed dos eventos assináveis (os mesmos dos webhooks de saída), por cursor do último evento lido.

A.5

Rotas do grupo A.5
RotaEscopoO que faz
GET /patients/lookuppacientes:lookupO telefone tem cadastro? Com a identidade confirmada, o paciente e a próxima consulta.
POST /patients/identifypacientes:lookupConfirma a identidade do paciente da conversa (CPF ou data de nascimento).
POST /patients/registerpacientes:cadastrarCadastro rápido de paciente novo (nome, celular da conversa e nascimento).
GET /availabilityagenda:readHorários livres num período (até 14 dias), por profissional ou de todos.
POST /appointmentsagenda:writeMarca para o paciente identificado (origem ia, status agendado).
POST /appointments/{id}/confirmagenda:writeConfirma a presença numa consulta do paciente identificado.
POST /appointments/{id}/cancelagenda:writeDesmarca uma consulta do paciente identificado.
POST /appointments/{id}/rescheduleagenda:writeRemarca uma consulta do paciente identificado (mesmo profissional).
GET /clinicclinica:readEndereço, horários, profissionais, especialidades e a política do assistente.
GET /billing/{patient_id}/opencobranca:readTítulos em aberto do paciente identificado, com link de pagamento e PIX do gateway (F3-16).
POST /quotes/{id}/acceptorcamento:aceitarAceite remoto de orçamento apresentado do paciente identificado (F3-16).

Ferramentas do agente

Rotas do grupo Ferramentas do agente
RotaEscopoO que faz
POST /ia/identificarpacientes:lookupFerramenta identificar_paciente.
GET /ia/clinicaclinica:readFerramenta dados_da_clinica (inclui a política do assistente).
GET /ia/horariosagenda:readFerramenta horarios_livres.
GET /ia/pacienteprontuario:read_iaFerramenta resumo_do_paciente (consultas e plano de tratamento; auditada).
POST /ia/pacientespacientes:cadastrarFerramenta cadastrar_paciente (cadastro rápido de paciente novo).
POST /ia/consultasagenda:writeFerramenta marcar_consulta.
POST /ia/consultas/{id}/confirmaragenda:writeFerramenta confirmar_consulta.
POST /ia/consultas/{id}/desmarcaragenda:writeFerramenta desmarcar_consulta.
POST /ia/consultas/{id}/remarcaragenda:writeFerramenta remarcar_consulta.

Sincronização

Rotas do grupo Sincronização
RotaEscopoO que faz
GET /patientspacientes:syncImportação e delta de pacientes para o CRM (cursor, updated_since).
POST /investcrm/revokeinvestcrmO 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.

Códigos de erro
HTTPcodeQuando
400dados, cursor_invalido, idempotenciaparâmetro, corpo, cursor ou Idempotency-Key inválido
401chave_invalidachave ausente, fora do formato, desconhecida, revogada ou vencida
403sem_escopo, politica, identificacaosem o escopo, política do assistente desligada ou falta identificar o paciente
404nao_encontradoo id não existe na clínica da chave
409conflito, ja_cadastrado, idempotencia_em_andamentohorário ocupado, telefone já cadastrado ou pedido repetido ainda em andamento
410cursor_expiradoo evento do cursor passou da retenção: recomece com since
422recusado, inadimplencia, idempotencia_conflitoregra da clínica recusou ou a mesma Idempotency-Key veio com outro pedido
429limitepassou do limite; respeite o Retry-After
503indisponivelAPI 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-Limit e X-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-Timestamp
  • X-OdontoInvest-Signature
  • X-OdontoInvest-Event
  • X-OdontoInvest-Delivery
  • X-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

  1. 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.

  2. 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.