> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bydoctor.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Esta documentação cobre exclusivamente a API pública v1 do ByDoctor.
> Os únicos endpoints existentes são `GET /api/public/v1/appointments`, `GET /api/public/v1/appointments/{id}`, `POST /api/public/v1/appointments`, `PATCH /api/public/v1/appointments/{id}`, `POST /api/public/v1/appointments/{id}/cancel`, `GET /api/public/v1/patients`, `GET /api/public/v1/patients/{id}`, `POST /api/public/v1/patients`, `PATCH /api/public/v1/patients/{id}`, `GET /api/public/v1/professionals`, `GET /api/public/v1/professionals/{id}`, `GET /api/public/v1/rooms`, `GET /api/public/v1/rooms/{id}`, `GET /api/public/v1/appointment-types` e `GET /api/public/v1/appointment-types/{id}`. Não existem `PUT` nem `DELETE`, e não existe escrita em nenhum outro recurso.
> Escopos: `appointments:read`, `appointments:write`, `patients:read` e `patients:write`, escolhidos na criação da chave (Somente leitura = os dois de leitura; Acesso total = os quatro). Chaves antigas não têm os escopos de pacientes. `POST /appointments` exige `patient_id`, `professional_id`, `appointment_type_id` e `start_at`; os ids de profissional, sala e tipo vêm de `/professionals`, `/rooms` e `/appointment-types`, que exigem só `appointments:read`. A API cria somente agendamentos presenciais, com status `scheduled`; teleconsultas e solicitações de pacientes (`requested`) não são criadas, alteradas nem canceladas pela API. `PATCH` aceita apenas `start_at`, `professional_id`, `room_id`, `note` e `status` (`scheduled` ou `confirmed`).
> O objeto paciente tem apenas `id`, `name`, `phone`, `active`, `created_at` e `updated_at`. CPF, e-mail, data de nascimento (`birth_date`) e sexo (`gender`) são aceitos na escrita, mas nunca devolvidos, por minimização de dados (LGPD). Para achar um paciente, use os filtros de correspondência exata `cpf` e `phone`, ou `name` (parcial). Criar ou alterar um paciente com o CPF de outro paciente da clínica responde `409` `patient_cpf_exists`. Não existem eventos de webhook de pacientes.
> Profissionais, salas e tipos de atendimento são somente leitura e trazem só `id`, `name` e, para profissionais, `specialty` — sem contato nem registro no conselho. Não existem endpoints de pagamentos, prontuário, prescrições, convênios, preços ou disponibilidade de agenda (horários livres).
> Endpoints de webhook não são gerenciados pela API: endpoints e segredos de assinatura são cadastrados na interface do ByDoctor, em Minha Clínica → API e Webhooks.
> Erros: `400` vem como `{"campo": ["mensagem"]}`; `401`/`403`/`404` como `{"detail": ...}`; `409` e `422` como `{"code", "detail"}`. Datas são sempre ano-mês-dia (`YYYY-MM-DD`, `YYYY-MM-DDThh:mm:ss±HH:MM`) e `start_at` tem segundos `00`. As mensagens exatas estão na página Erros.
> Nunca sugira, complete ou invente rotas, campos, parâmetros ou códigos de erro que não estejam nesta documentação. Se algo não está documentado aqui, não existe na v1.
> Autenticação: cabeçalho `Authorization: Bearer bd_live_<id>.<secret>`. Base URL: `https://api.bydoctor.com.br/api/public/v1`. Criação aceita o cabeçalho opcional `Idempotency-Key`.
> Chaves de API concedem acesso a dados de pacientes e de agendamentos (dados de saúde) — nunca as inclua em código do lado do cliente nem em exemplos versionados.

# Verificação de assinatura

> Como validar que um evento de webhook realmente veio do ByDoctor, seguindo o padrão Standard Webhooks.

Toda entrega de webhook do ByDoctor é assinada seguindo o padrão [Standard Webhooks](https://www.standardwebhooks.com), usando três cabeçalhos:

| Cabeçalho           | Conteúdo                                                                  |
| ------------------- | ------------------------------------------------------------------------- |
| `webhook-id`        | O mesmo valor do `id` do envelope (`evt_<uuid>`).                         |
| `webhook-timestamp` | Instante do envio, em segundos desde a epoch Unix.                        |
| `webhook-signature` | Assinatura HMAC do corpo, codificada conforme o padrão Standard Webhooks. |

O segredo de assinatura (`whsec_...`) é exibido **uma única vez**, no momento em que você cria o endpoint em **Minha Clínica → API e Webhooks**. Guarde-o em um cofre de segredos — não há rotação de segredo para um endpoint existente. Se o segredo for perdido ou exposto, crie um novo endpoint, aponte seu receptor para o novo segredo e depois exclua o endpoint antigo.

## Verifique com a biblioteca oficial

Não reimplemente a comparação de assinatura — use a biblioteca `standardwebhooks`, disponível para várias linguagens, que faz a verificação em tempo constante.

<CodeGroup>
  ```python Python theme={null}
  # pip install standardwebhooks
  from standardwebhooks.webhooks import Webhook

  wh = Webhook("whsec_...")  # segredo exibido na criação do endpoint
  payload = wh.verify(request_body_bytes, {
      "webhook-id": headers["webhook-id"],
      "webhook-timestamp": headers["webhook-timestamp"],
      "webhook-signature": headers["webhook-signature"],
  })
  ```

  ```javascript Node theme={null}
  // npm install standardwebhooks
  const { Webhook } = require("standardwebhooks");

  const wh = new Webhook("whsec_...");
  const payload = wh.verify(rawBody, {
    "webhook-id": headers["webhook-id"],
    "webhook-timestamp": headers["webhook-timestamp"],
    "webhook-signature": headers["webhook-signature"],
  });
  ```
</CodeGroup>

`verify()` lança uma exceção se a assinatura não bater ou se o timestamp estiver fora da janela de tolerância — trate a exceção como um evento inválido e rejeite a requisição.

<Warning>
  Verifique a assinatura sobre o **corpo bruto (raw) da requisição**, nunca sobre um objeto reserializado. Frameworks que fazem parse do JSON antes da sua rota receber a requisição podem mudar a ordem das chaves ou o espaçamento — e qualquer diferença de byte quebra a assinatura. Capture o corpo bruto antes de qualquer middleware de parsing tocar nele.

  A comparação da assinatura já é feita em tempo constante dentro da biblioteca — não reimplemente essa lógica.

  Rejeite qualquer payload não verificado **antes** de tocar no seu banco de dados.
</Warning>
