API da agenda
Para parceiros e sistemas da clínica lerem a agenda do Trilio e do Órbita, verem os horários livres, marcarem, cancelarem e remarcarem consultas, e receberem um aviso quando a agenda muda. Versão v1.
Disponível no plano Clínica em diante. Quem administra a clínica gera a chave em
Configurações › Conexões e saúde › Para desenvolvedores, com o que ela pode fazer: ler a agenda (agenda:leitura)
e marcar na agenda (agenda:escrita).
Autenticação
Toda chamada leva a chave no cabeçalho. A chave aparece uma vez, quando é criada; a clínica pode revogá-la a qualquer momento.
curl https://api.quatrouno.com.br/v1/compromissos?inicio=2026-11-09&fim=2026-11-13 \
-H "Authorization: Bearer qu_live_..."
Limite: 120 chamadas por minuto por chave. Toda chamada fica registrada para a clínica.
Rotas
| Rota | Devolve |
|---|---|
GET /v1/compromissos?inicio&fim | O que ocupa a agenda na janela (até 62 dias). Filtros opcionais: profissional (id) e atualizado_desde (data e hora com fuso). |
GET /v1/compromissos/{id} | Um compromisso. |
GET /v1/profissionais | Médicos e técnicos com agenda: id, nome, tipo. |
GET /v1/procedimentos | id, nome, duracao_minutos, cirurgico. |
GET /v1/locais | id, nome. |
As respostas vêm em {"dados": ...}.
Marcar na agenda
Precisa de uma chave com agenda:escrita. O parceiro marca como a recepção da clínica marcaria: em qualquer horário livre da
agenda, não só nos que aparecem no link de agendamento.
| Rota | Faz |
|---|---|
GET /v1/disponibilidade?procedimentos&inicio&fim | Os horários livres para 1 a 5 procedimentos (ids separados por vírgula), numa janela de até 31 dias. Filtro opcional: profissional. Serve a chaves de leitura ou de escrita. |
POST /v1/agendamentos | Marca. Exige o cabeçalho Idempotency-Key. Responde 201. |
POST /v1/agendamentos/{id}/remarcar | Muda data, hora, duração ou profissional de uma marcação feita por esta chave. O id continua o mesmo. |
POST /v1/agendamentos/{id}/cancelar | Cancela uma marcação feita por esta chave. Corpo opcional: {"motivo": "..."}. |
{id} é o agendamento_id ou o id do compromisso (ag:...). A chave só cancela e remarca o que ela mesma marcou;
o resto responde 404.
Horário livre
{ "data": "2026-11-20", "hora": "09:20",
"inicio": "2026-11-20T09:20:00-03:00", "fim": "2026-11-20T09:40:00-03:00",
"duracao_minutos": 20, // o bloco da agenda
"duracao_referencia": 40, // quanto os procedimentos somam com este profissional
"profissional_id": "…" }
Marcar
curl -X POST https://api.quatrouno.com.br/v1/agendamentos \
-H "Authorization: Bearer qu_live_..." \
-H "Idempotency-Key: 6f1c2d4e-…" \
-H "Content-Type: application/json" \
-d '{
"paciente": { "nome": "Maria da Silva", "telefone": "21987654321", "nascimento": "1980-01-31" },
"profissional_id": "…", "procedimento_ids": ["…"],
"data": "2026-11-20", "hora": "09:20"
}'
- Paciente:
{"id": "…"}de um paciente da clínica, ou nome, telefone com DDD e nascimento. Com os três, procuramos o cadastro como o link de agendamento procura; se não houver, ele é criado. - Opcionais:
duracao_minutos(sem ela, a que a recepção usaria),convenio_id(sem ele, particular),observacao(até 500 caracteres, fica para a equipe) eencaixe. - Encaixe: horário ocupado, fora dos blocos da agenda, aparelho em uso, folga do profissional ou clínica fechada recusam a marcação. Envie
"encaixe": truepara marcar assim mesmo, como a recepção faz; a resposta traz emavisoso que foi aceito. - Idempotency-Key: um valor único por marcação (um UUID, por exemplo). Repetir a chamada com a mesma chave e o mesmo corpo, em até 24 horas, devolve a mesma resposta, com o cabeçalho
Idempotent-Replayed: true, e não marca de novo. A mesma chave com outro corpo responde409 idempotencia_conflito. - Resposta:
{"dados": {"id": "ag:…", "agendamento_id": "…", "compromisso": {…}, "encaixe": false, "avisos": []}}, com o paciente conforme o nível da chave. - Remarcar leva
{"data", "hora"}e, se quiser,duracao_minutos,profissional_ideencaixe. Cancelar duas vezes devolve o mesmo resultado.
Se a assinatura da clínica estiver com pagamento atrasado há mais de 7 dias, marcar e remarcar respondem 403 acesso_suspenso; ler e cancelar continuam.
Compromisso
{
"id": "ag:5b1c...", // ag:, dia:, exc: ou blc:<id>:<AAAAMMDD>
"tipo": "consulta", // consulta, exame, dia_cirurgico, bloqueio
"modulo": "trilio",
"origem": "interna", // interna, diagy, api, google_calendar, ics, sistema_externo
"somente_leitura": false, // verdadeiro quando veio de outro sistema
"inicio": "2026-11-10T12:00:00+00:00",
"fim": "2026-11-10T12:20:00+00:00",
"dia_inteiro": false,
"profissional_id": "…", "recurso_tecnico_id": null, "local_id": null, "local_nome": null,
"status": "booked", // pending, booked, fulfilled, noshow
"paciente_nome": "J.S.", // conforme o nível da chave
"servico": "Retorno",
"observacao": null,
"itens": null, // no dia cirúrgico, as cirurgias do dia
"atualizado_em": "2026-11-07T18:31:02+00:00"
}
O que sai do paciente depende do nível escolhido pela clínica para a chave: só ocupado (sem paciente e sem serviço),
tipo (sem paciente), iniciais ou nome completo (com paciente_id). Prontuário nunca sai.
Webhooks
A clínica cadastra uma URL https e recebe um POST quando um compromisso é criado, alterado ou cancelado.
POST https://seu-sistema/webhook
X-Event-Id: ev_123
X-Timestamp: 1762540000
X-Signature: <hex de HMAC-SHA256(segredo, X-Timestamp + "." + corpo)>
{
"eventId": "ev_123",
"eventType": "compromisso.alterado",
"eventVersion": 1,
"clinicId": "…",
"versao": 123,
"occurredAt": "2026-11-07T18:31:02+00:00",
"payload": { "ref": "ag:5b1c...", "compromissos": [ { …estado atual… } ] }
}
- Estado atual: o corpo traz o compromisso como ele está na hora do envio. Lista vazia: ele não está mais na agenda.
- Ordem: guarde a maior
versaode cadarefe ignore as menores. Um reenvio repete oeventId. - Conferência: calcule a assinatura com o segredo do webhook e recuse
X-Timestampcom mais de 5 minutos de diferença. - Resposta: qualquer 2xx em até 10 segundos. Sem isso, tentamos de novo em 1, 5, 15 e 60 minutos e depois de hora em hora, por 24 horas. Responder
410pausa o webhook.
// Node: conferir a assinatura
import crypto from "node:crypto";
const esperada = crypto.createHmac("sha256", segredo).update(`${timestamp}.${corpoCru}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(assinatura));
Erros
Sempre {"erro": {"codigo", "mensagem"}}, com a mensagem em português.
| Status | Código |
|---|---|
| 400 | parametro_invalido, periodo_invalido, idempotencia_ausente |
| 401 | chave_ausente, chave_invalida, chave_revogada |
| 402 | plano |
| 403 | escopo_insuficiente, acesso_suspenso |
| 404 | nao_encontrado |
| 405 | metodo_nao_permitido |
| 409 | horario_ocupado, aparelho_ocupado, profissional_de_folga, clinica_fechada, agendamento_encerrado, limite_agendas, idempotencia_conflito |
| 422 | parametro_invalido (campo do corpo), paciente_invalido, profissional_invalido, procedimento_invalido, convenio_invalido, horario_passado, fora_da_grade, aparelho_inexistente |
| 429 | limite (com Retry-After) |
| 500 | erro_interno |