
# Agendamentos

A API de Agendamentos permite que você marque compromissos para seus contatos em seus tipos de evento, além de buscar, listar, atualizar, cancelar ou excluí-los. Ela também responde à pergunta que surge primeiro na maioria dos fluxos de agendamento — quais horários estão realmente livres — e cobre o lado do calendário: listar os Google Calendars que você conectou e importar eventos que já existem neles. Quando uma conexão com o Google Calendar está ativa, o evento de calendário correspondente é criado e mantido sincronizado automaticamente em segundo plano. Restaurantes que usam Zenchef, Formitable, OpenTable ou TheFork para seus próprios sistemas de reserva também podem ser verificados e conectados aqui, para que o Agente de IA reserve mesas reais em vez de agendamentos internos — e empresas de agendamento que gerenciam seus horários no Trafft podem se conectar da mesma maneira.

Todos os caminhos nesta página são relativos à URL base `https://api.dmchamp.com/v1`. Cada solicitação precisa da sua chave de API — consulte [Autenticação](authentication.md) para obter a lista completa de formas de enviá-la. Os exemplos abaixo usam o cabeçalho `X-API-Key`, com um exemplo cURL mostrando também o formato de consulta `?apiKey=`.

> **Eventos vs. agendamentos:** Um *tipo de evento* é uma definição de intervalo reservável (o tipo de reunião, sua duração, suas salas). Um *agendamento* é uma instância reservada de um tipo de evento para um contato específico. Você reserva um agendamento referenciando o contato e o tipo de evento.

---

## O objeto de agendamento

Cada endpoint que retorna um agendamento usa a mesma estrutura:

| Campo | Descrição |
|---|---|
| `id` | ID exclusivo do agendamento. |
| `contact_id` | ID do contato com o qual o agendamento foi marcado. |
| `event_id` | ID do tipo de evento no qual o agendamento foi marcado. |
| `status` | `Confirmed` ou `Canceled`. |
| `start_time` | Início do agendamento, ISO 8601 em UTC. |
| `end_time` | Fim do agendamento, ISO 8601 em UTC. |
| `created_at` | Quando o agendamento foi criado. |
| `last_modified_at` | Quando o agendamento foi alterado pela última vez. |
| `room_name` | Sala ou recurso no qual o agendamento foi marcado, quando o tipo de evento usa salas. |
| `description` | Descrição de formato livre do agendamento. |
| `summary` | Resumo ou título curto. |
| `cancelation_reason` | Motivo fornecido quando o agendamento foi cancelado, se houver. |
| `google_calendar_event_id` | ID do evento vinculado do Google Agenda. Definido assim que a sincronização do calendário for concluída; `null` quando nenhum calendário estiver conectado ou enquanto a sincronização ainda estiver em andamento. |
| `calendar_synced` | `true` assim que o agendamento estiver vinculado a um evento de calendário. |
| `imported` | `true` quando o agendamento foi importado de um calendário externo em vez de reservado diretamente. |
| `is_recurring` | `true` quando o agendamento faz parte de uma série recorrente. |
| `recurrence_frequency` | Com que frequência o agendamento se repete, quando recorrente. |
| `recurring_event_id` | ID da série recorrente à qual este agendamento pertence. |
| `recurring_interval` | Intervalo entre repetições, quando recorrente. |
| `recurring_sequence` | Posição deste agendamento dentro de sua série recorrente. |
| `end_after_x_occurrences` | Número de ocorrências após as quais a série recorrente termina. |
| `booking_provider` | Sistema de origem de onde veio a reserva, quando feita por meio de um provedor de reserva conectado. |

> **Sobre a sincronização de calendário:** Logo após você reservar ou alterar um agendamento, `google_calendar_event_id` pode ainda ser `null` e `calendar_synced` pode ser `false` porque a sincronização é executada em segundo plano um momento depois. Busque o agendamento novamente pouco tempo depois para ver os campos de calendário preenchidos.

---

## Encontrar horários disponíveis

`GET /appointments/available-slots`

Retorna os horários que estão genuinamente livres em um tipo de evento entre dois momentos. Esta é normalmente a **primeira** chamada em um fluxo de agendamento: mostre esses horários, deixe a pessoa escolher um e, em seguida, envie o horário escolhido para [Agendar um compromisso](#book-an-appointment).

A resposta já leva em conta o horário de funcionamento e a duração do intervalo do próprio tipo de evento, suas salas, compromissos que você já agendou nele e tudo o que está bloqueado nos Google Calendars conectados — portanto, um horário que aparece aqui é um que você pode reservar.

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `event_id` | Sim | O tipo de evento a ser verificado. Deve pertencer à sua conta. |
| `start_time` | Sim | Início da janela para a qual você deseja horários, data e hora em ISO 8601. |
| `end_time` | Sim | Fim da janela, data e hora em ISO 8601. O dia final completo está incluído. |

Os resultados são retornados agrupados por dia — e, quando o tipo de evento usa salas, um grupo por sala por dia:

| Campo | Descrição |
|---|---|
| `date` | O dia que o grupo cobre, escrito `DD/MM/YYYY`. |
| `day` | Nome do dia da semana em letras minúsculas, por exemplo `monday`. |
| `room_name` | A sala ou recurso ao qual este grupo pertence, quando o tipo de evento usa salas. |
| `available_slots` | Os blocos reserváveis naquele dia, do mais cedo para o mais tarde. |

Cada entrada em `available_slots` possui:

| Campo | Descrição |
|---|---|
| `start_time` | Início do bloco como `HH:mm`. |
| `end_time` | Fim do bloco como `HH:mm`. |
| `available` | `true` — apenas o tempo livre é retornado. |
| `spots_left` | Quantas reservas ainda cabem neste bloco. Presente apenas em tipos de evento que aceitam mais de uma reserva por intervalo. |

> **Os horários são locais ao tipo de evento, não UTC.** `date`, `start_time` e `end_time` são valores de relógio de parede no fuso horário do próprio tipo de evento (sua substituição, ou o fuso horário da sua conta quando não houver nenhum). [Agendar um compromisso](#book-an-appointment) espera um instante UTC em ISO 8601, portanto, converta o horário que você escolheu antes de enviá-lo.

**cURL**

```bash
curl "https://api.dmchamp.com/v1/appointments/available-slots?event_id=event_xyz789&start_time=2026-06-15T00:00:00.000Z&end_time=2026-06-19T00:00:00.000Z" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  event_id: "event_xyz789",
  start_time: "2026-06-15T00:00:00.000Z",
  end_time: "2026-06-19T00:00:00.000Z",
});
const res = await fetch(
  `https://api.dmchamp.com/v1/appointments/available-slots?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.data);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.dmchamp.com/v1/appointments/available-slots",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={
        "event_id": "event_xyz789",
        "start_time": "2026-06-15T00:00:00.000Z",
        "end_time": "2026-06-19T00:00:00.000Z",
    },
)
print(res.json()["data"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": [
    {
      "date": "15/06/2026",
      "day": "monday",
      "room_name": "Room A",
      "available_slots": [
        { "start_time": "10:00", "end_time": "10:30", "available": true },
        { "start_time": "10:30", "end_time": "11:00", "available": true }
      ]
    },
    {
      "date": "16/06/2026",
      "day": "tuesday",
      "room_name": "Room A",
      "available_slots": [
        { "start_time": "09:00", "end_time": "09:30", "available": true, "spots_left": 2 }
      ]
    }
  ]
}
```

Um dia sem nada livre simplesmente não aparece. A falta de `event_id`, `start_time` ou `end_time` retorna `400`; um tipo de evento que não está na sua conta retorna `404`.

---

## Reservar um agendamento

`POST /appointments`

Reserva um novo agendamento para um contato em um dos seus tipos de evento. O horário de término é calculado automaticamente a partir da duração do intervalo do tipo de evento.

A reserva passa por uma verificação de conflito: se o intervalo solicitado sobrepuser um agendamento confirmado existente no mesmo tipo de evento, a solicitação falhará com um `409` e nada será criado.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `contact_id` | Sim | ID do contato para o qual reservar. Deve pertencer à sua conta. |
| `event_id` | Sim | ID do tipo de evento no qual reservar. Deve pertencer à sua conta. |
| `start_time` | Sim | Início desejado como uma data-hora ISO 8601. |
| `room_name` | Não | Nome da sala ou recurso, quando o tipo de evento usa salas. |

**cURL** (usando o formato de consulta `?apiKey=`)

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "start_time": "2026-06-15T10:00:00.000Z",
    "room_name": "Room A"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/appointments", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contact_id: "contact_abc123",
    event_id: "event_xyz789",
    start_time: "2026-06-15T10:00:00.000Z",
    room_name: "Room A",
  }),
});
const data = await res.json();
console.log(data.appointment_id);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/appointments",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "contact_id": "contact_abc123",
        "event_id": "event_xyz789",
        "start_time": "2026-06-15T10:00:00.000Z",
        "room_name": "Room A",
    },
)
print(res.json()["appointment_id"])
```

**Resposta** (`201 Created`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678",
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-15T10:00:00.000Z",
    "end_time": "2026-06-15T10:30:00.000Z",
    "created_at": "2026-06-10T09:00:00.000Z",
    "last_modified_at": "2026-06-10T09:00:00.000Z",
    "room_name": "Room A",
    "google_calendar_event_id": null,
    "calendar_synced": false
  }
}
```

---

## Obter um agendamento

`GET /appointments/{appointmentId}`

Retorna um único agendamento pelo seu ID, incluindo seu estado de sincronização de calendário.

**cURL**

```bash
curl "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointment);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["appointment"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-15T10:00:00.000Z",
    "end_time": "2026-06-15T10:30:00.000Z",
    "room_name": "Room A",
    "google_calendar_event_id": "abc123googleevent",
    "calendar_synced": true
  }
}
```

---

## Listar agendamentos

`GET /appointments`

Lista os agendamentos da sua conta, do mais recente para o mais antigo, com paginação baseada em cursor.

| Parâmetro de consulta | Obrigatório | Descrição |
|---|---|---|
| `contact_id` | Não | Retorna apenas agendamentos para este contato. Listagens filtradas por contato incluem **apenas agendamentos confirmados**. |
| `date` | Não | Retorna apenas agendamentos neste dia do calendário (`YYYY-MM-DD`). **Requer `contact_id`.** |
| `status` | Não | Filtra por `Confirmed` ou `Canceled`. Disponível apenas **sem** `contact_id`. |
| `limit` | Não | Tamanho da página, um número inteiro entre 1 e 100. O padrão é `50`. |
| `cursor` | Não | O valor `next_cursor` de uma resposta anterior. |

Algumas regras para ter em mente:

- **Sem filtros**, você obtém todos os agendamentos da conta, página por página.
- **Por contato** — defina `contact_id` para ver os agendamentos confirmados de um contato. Você pode restringir isso a um único dia passando também `date`.
- **Por status** — defina `status` (sem `contact_id`) para listar apenas agendamentos `Confirmed` ou apenas `Canceled` em toda a conta.
- O filtro `date` sem `contact_id`, ou `status=Canceled` junto com `contact_id`, retorna um `400`.

**cURL**

```bash
curl "https://api.dmchamp.com/v1/appointments?contact_id=contact_abc123&date=2026-06-15" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const params = new URLSearchParams({
  contact_id: "contact_abc123",
  date: "2026-06-15",
});
const res = await fetch(
  `https://api.dmchamp.com/v1/appointments?${params}`,
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointments, data.next_cursor);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.dmchamp.com/v1/appointments",
    headers={"X-API-Key": "YOUR_API_KEY"},
    params={"contact_id": "contact_abc123", "date": "2026-06-15"},
)
data = res.json()
print(data["appointments"], data["next_cursor"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "appointments": [
    {
      "id": "aBcD1234eFgH5678",
      "contact_id": "contact_abc123",
      "event_id": "event_xyz789",
      "status": "Confirmed",
      "start_time": "2026-06-15T10:00:00.000Z",
      "end_time": "2026-06-15T10:30:00.000Z",
      "calendar_synced": true
    }
  ],
  "next_cursor": null
}
```

Para navegar pelos resultados, passe o `next_cursor` de uma resposta como o `cursor` da próxima solicitação. Continue até que `next_cursor` seja `null`. Consulte [Erros e Paginação](errors-and-pagination.md) para o padrão de paginação compartilhado.

---

## Atualizar um agendamento

`PUT /appointments/{appointmentId}`

Remarque um agendamento ou altere seus detalhes. Envie apenas os campos que deseja alterar — pelo menos um é obrigatório. O início e o fim combinados devem permanecer em ordem cronológica (`end_time` deve ser posterior a `start_time`). As alterações são sincronizadas automaticamente com o evento de calendário vinculado.

| Campo | Descrição |
|---|---|
| `start_time` | Nova data e hora de início, no formato ISO 8601. |
| `end_time` | Nova data e hora de término, no formato ISO 8601. Deve ser posterior ao horário de início. |
| `room_name` | Novo nome da sala ou recurso. |
| `description` | Nova descrição, ou `null` para limpá-la. |
| `summary` | Novo resumo, ou `null` para limpá-lo. |

**cURL**

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "start_time": "2026-06-16T10:00:00.000Z",
    "end_time": "2026-06-16T10:30:00.000Z"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
  {
    method: "PUT",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      start_time: "2026-06-16T10:00:00.000Z",
      end_time: "2026-06-16T10:30:00.000Z",
    }),
  }
);
const data = await res.json();
console.log(data.appointment);
```

**Python**

```python
import requests

res = requests.put(
    "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={
        "start_time": "2026-06-16T10:00:00.000Z",
        "end_time": "2026-06-16T10:30:00.000Z",
    },
)
print(res.json()["appointment"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678",
  "appointment": {
    "id": "aBcD1234eFgH5678",
    "contact_id": "contact_abc123",
    "event_id": "event_xyz789",
    "status": "Confirmed",
    "start_time": "2026-06-16T10:00:00.000Z",
    "end_time": "2026-06-16T10:30:00.000Z",
    "calendar_synced": true
  }
}
```

---

## Cancelar um agendamento

`POST /appointments/{appointmentId}/cancel`

Cancela um agendamento confirmado, registrando opcionalmente um motivo. O agendamento permanece em sua conta com o status `Canceled`, e o evento de calendário vinculado é removido automaticamente em segundo plano. Cancelar um agendamento já cancelado retorna um `400`.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `cancellation_reason` | Não | Motivo do cancelamento, armazenado no agendamento. |

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "cancellation_reason": "Client asked to reschedule next month"
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      cancellation_reason: "Client asked to reschedule next month",
    }),
  }
);
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678/cancel",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"cancellation_reason": "Client asked to reschedule next month"},
)
print(res.json()["success"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "appointment_id": "aBcD1234eFgH5678"
}
```

---

## Excluir um agendamento

`DELETE /appointments/{appointmentId}`

Exclui permanentemente um agendamento e suas referências. Se você deseja apenas cancelar a reserva mantendo o registro, use [cancelar](#cancel-an-appointment) em vez disso.

**cURL**

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch(
  "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
```

**Python**

```python
import requests

res = requests.delete(
    "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true
}
```

---

## Listar seus Google Calendars conectados

`GET /appointments/google-calendars`

Retorna os Google Calendars disponíveis nesta conta, diretamente do Google — útil para mostrar ao titular da conta um seletor de qual calendário importar abaixo, ou apenas para confirmar que a conexão está ativa.

Isso só funciona depois que a conta tiver conectado o Google Calendar (Configurações → Integrações) com pelo menos acesso de leitura. Se não tiver, ou se o acesso concedido não incluir mais o escopo de leitura de calendário, você receberá um `400` solicitando que você o conecte (ou reconecte).

**cURL**

```bash
curl "https://api.dmchamp.com/v1/appointments/google-calendars" \
  -H "X-API-Key: YOUR_API_KEY"
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/appointments/google-calendars", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data);
```

**Python**

```python
import requests

res = requests.get(
    "https://api.dmchamp.com/v1/appointments/google-calendars",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["data"])
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": [
    {
      "id": "primary",
      "summary": "jane@example.com",
      "timeZone": "America/New_York",
      "accessRole": "owner",
      "primary": true
    },
    {
      "id": "abcdefg1234567890@group.calendar.google.com",
      "summary": "Bookings",
      "timeZone": "America/New_York",
      "accessRole": "writer"
    }
  ]
}
```

Cada entrada segue o formato [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) do próprio Google, portanto, os nomes dos campos seguem a `camelCase` do Google, não o `snake_case` usual desta API — esses são dados do Google passados como estão, não os nossos. Uma conexão ausente ou revogada retorna `400` com um erro explicando que o Google Calendar precisa ser conectado (ou reconectado).

---

## Importar eventos de um Google Calendar

`POST /appointments/import-calendar-events`

Puxa os eventos que já estão no(s) Google Calendar(s) conectado(s) de uma campanha ou Agente de IA e os transforma em agendamentos — útil na primeira vez que você conecta um calendário que já possui reservas. Isso pode levar algum tempo (cada evento passa por uma extração para descobrir para quem é), por isso nunca é executado em linha: a solicitação enfileira um trabalho em segundo plano e retorna um `job_id` para consulta.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `campaign_id` | Um destes dois | A campanha cujo(s) calendário(s) conectado(s) será(ão) usado(s) para importação. |
| `agent_id` | Um destes dois | O Agente de IA cujo(s) calendário(s) conectado(s) será(ão) usado(s) para importação. |
| `identifier` | Sim | `"EMAIL"` ou `"PHONE_NUMBER"` — qual informação de contato extrair de cada evento de calendário para corresponder ou criar o contato ao qual ele pertence. |

Envie exatamente um entre `campaign_id` / `agent_id`, nunca ambos e nunca nenhum — qualquer combinação retorna um `400`. O que você enviar deve pertencer à sua conta, caso contrário, você receberá um `404`.

**cURL**

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/import-calendar-events?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_abc123",
    "identifier": "EMAIL"
  }'
```

**JavaScript**

```javascript
const res = await fetch("https://api.dmchamp.com/v1/appointments/import-calendar-events", {
  method: "POST",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    agent_id: "agent_abc123",
    identifier: "EMAIL",
  }),
});
const data = await res.json();
console.log(data.job_id);
```

**Python**

```python
import requests

res = requests.post(
    "https://api.dmchamp.com/v1/appointments/import-calendar-events",
    headers={
        "X-API-Key": "YOUR_API_KEY",
        "Content-Type": "application/json",
    },
    json={"agent_id": "agent_abc123", "identifier": "EMAIL"},
)
print(res.json()["job_id"])
```

**Resposta** (`202 Accepted`):

```json
{
  "success": true,
  "job_id": "jK9mQ2xR7pL4wN1t",
  "status": "queued",
  "campaign_id": null,
  "agent_id": "agent_abc123"
}
```

`campaign_id` e `agent_id` retornam o que você enviou; o outro é sempre `null`.

### Consultar o trabalho de importação

`GET /appointments/import-calendar-events/{jobId}`

```bash
curl "https://api.dmchamp.com/v1/appointments/import-calendar-events/jK9mQ2xR7pL4wN1t" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "job_id": "jK9mQ2xR7pL4wN1t",
  "status": "completed",
  "message": "Imported 12 events as appointments.",
  "error": null
}
```

| `status` | Significado |
|---|---|
| `queued` | Ainda não iniciado. Continue consultando. |
| `processing` | A importação está em execução. Continue consultando. |
| `completed` | Concluído — `message` contém um breve resumo legível. |
| `failed` | Algo deu errado — `error` contém o motivo. |

`GET` em um `jobId` que não existe (ou pertence a uma conta diferente) retorna `404`.

---

<a id="restaurant-booking-integrations-zenchef-formitable"></a>

## Integrações de agendamento externo (Zenchef / Formitable / OpenTable / TheFork / Trafft)

Zenchef e Formitable são sistemas de reserva de restaurantes nos quais seu Agente de IA pode reservar mesas reais; o [Trafft](#trafft) é uma plataforma de agendamento para empresas que trabalham com horários, conectada uma vez por conta, em vez de por restaurante. As duas plataformas de restaurante possuem um **widget de reserva público e não autenticado** (`https://api.dmchamp.com/v1/zenchef-widget/...` e `https://api.dmchamp.com/v1/formitable-widget/...`) que é renderizado dentro do chat para o cliente — essas rotas de widget são páginas HTML simples destinadas a serem abertas em um navegador, não endpoints de API JSON, portanto, não estão documentadas aqui. O que segue são os endpoints de gerenciamento de conta: verificar se um ID de restaurante pertence ao titular da conta e, em seguida, adicioná-lo, atualizá-lo ou removê-lo.

### Zenchef

Conectar um restaurante Zenchef é uma verificação de duas etapas, para que o titular da conta prove que realmente administra o restaurante antes que ele seja conectado ao bot: primeiro, verifique se o ID existe (sem revelar o nome), depois peça que eles mesmos digitem o nome do restaurante e verifique se corresponde.

**Etapa 1 — Verificar se um ID de restaurante existe**

`POST /appointments/zenchef-restaurants/check`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_id` | Sim | O ID do restaurante Zenchef a ser verificado. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/check?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345" }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": { "exists": true, "requiresNameVerification": true }
}
```

`exists: false` significa que nenhum restaurante Zenchef possui esse ID — nada mais a fazer. Limitado a 10 verificações a cada 5 minutos por conta; exceder esse limite retorna `429`.

**Etapa 2 — Verificar o nome do restaurante**

`POST /appointments/zenchef-restaurants/verify-name`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_id` | Sim | O ID do restaurante Zenchef da etapa 1. |
| `user_input_name` | Sim | O nome que o titular da conta digitou — comparado com o nome real do restaurante no Zenchef (insensível a maiúsculas/minúsculas e espaços em branco). |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/verify-name?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345", "user_input_name": "The Blue Door Bistro" }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "id": "12345",
      "name": "The Blue Door Bistro",
      "address": "1 Rue de Rivoli, Paris",
      "status": "active"
    }
  }
}
```

`verified: false` significa que o nome não correspondeu — `restaurantDetails` é omitido, peça ao titular da conta que tente novamente. Limitado a 3 tentativas a cada 5 minutos (mais rigoroso que a verificação de existência, já que esta é a etapa de prova real). Um `restaurant_id` que não é mais resolvido no Zenchef retorna `404`.

**Etapa 3 — Salvar o restaurante**

`POST /appointments/zenchef-restaurants`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_id` | Sim | 1–64 caracteres, letras/números/sublinhado/hífen. |
| `restaurant_name` | Sim | O nome do restaurante verificado da etapa 2. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "12345", "restaurant_name": "The Blue Door Bistro" }'
```

**Resposta** (`201 Created`):

```json
{ "success": true, "data": { "restaurantId": "12345" } }
```

**Atualizar um restaurante Zenchef salvo**

`PUT /appointments/zenchef-restaurants/{restaurantId}`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_name` | Não | Novo nome de exibição. |
| `is_active` | Não | Defina `false` para impedir que o bot faça reservas neste restaurante sem removê-lo. |

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/12345" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**Resposta** (`200 OK`): mesmo formato da resposta de salvamento acima.

**Remover um restaurante Zenchef**

`DELETE /appointments/zenchef-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/12345" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "12345" } }`

Um `restaurantId` que não está atualmente na conta retorna `404` ao atualizar ou excluir.

### Formitable

O Formitable não precisa da prova de nome em duas etapas que o Zenchef exige — seus IDs de restaurante já são delimitados por empresa, portanto, uma chamada de verificação é suficiente. Ele também possui uma consulta de detalhes usada para armazenar em cache a URL do site do restaurante durante a configuração.

**Verificar um ID de restaurante**

`POST /appointments/formitable-restaurants/verify`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_id` | Sim | O ID do restaurante Formitable. |
| `language` | Não | Tag de idioma para a solicitação de teste. O padrão é `"nl"`. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/formitable-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "the-blue-door", "language": "en" }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "the-blue-door",
      "productCount": 4,
      "sampleProductTitle": "Dinner for two",
      "language": "en"
    }
  }
}
```

Um `restaurant_id` que o Formitable não reconhece retorna `404`. Limitado a 10 tentativas a cada 5 minutos por conta.

**Obter detalhes do restaurante**

`GET /appointments/formitable-restaurants/{restaurantId}/details?language=en`

Busca o perfil público do restaurante no Formitable, incluindo seu site — usado para armazenar em cache a URL do site durante a configuração do restaurante. `language` é um parâmetro de consulta opcional, com padrão para `"en"`.

```bash
curl "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door/details?language=en" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "uid": "the-blue-door",
    "name": "The Blue Door Bistro",
    "website": "https://thebluedoorbistro.com",
    "email": "info@thebluedoorbistro.com",
    "telephone": "+31201234567",
    "streetAddress": "Prinsengracht 1",
    "zipcode": "1015 AB",
    "city": "Amsterdam",
    "country": "Netherlands",
    "countryCode": "NL",
    "currency": "EUR"
  }
}
```

**Salvar o restaurante**

`POST /appointments/formitable-restaurants`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_id` | Sim | 1–64 caracteres, letras/números/sublinhado/hífen. |
| `restaurant_name` | Sim | Nome de exibição. |
| `language` | Sim | Tag de idioma ISO, ex: `"en"` ou `"en-GB"`. |
| `website_url` | Não | O site do restaurante, a partir da consulta de detalhes acima. Deve ser `http(s)://`. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/formitable-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "restaurant_id": "the-blue-door",
    "restaurant_name": "The Blue Door Bistro",
    "language": "en",
    "website_url": "https://thebluedoorbistro.com"
  }'
```

**Resposta** (`201 Created`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

**Atualizar um restaurante Formitable salvo**

`PUT /appointments/formitable-restaurants/{restaurantId}`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_name` | Não | Novo nome de exibição. |
| `language` | Não | Nova tag de idioma ISO. |
| `is_active` | Não | Defina `false` para impedir que o bot faça reservas neste restaurante sem removê-lo. |
| `website_url` | Não | Nova URL do site. |

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**Resposta** (`200 OK`): mesmo formato da resposta de salvamento acima.

**Remover um restaurante Formitable**

`DELETE /appointments/formitable-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "the-blue-door" } }`

Um `restaurantId` que não está atualmente na conta retorna `404` ao atualizar ou excluir.

### OpenTable

Os restaurantes do OpenTable são acessados por meio das credenciais de parceiro do OpenTable da plataforma, e o OpenTable só permite que essas credenciais vejam restaurantes que conectaram a listagem da plataforma dentro do Marketplace de Integrações do OpenTable. Portanto, assim como no Formitable, uma chamada de verificação é suficiente: um ID de Restaurante acessível (o "RID" numérico) prova tanto que o restaurante existe quanto que ele conectou a integração. Até que a listagem de parceiro do OpenTable seja habilitada na plataforma, a chamada de verificação responde `503`.

**Verificar um restaurante OpenTable**

`POST /appointments/opentable-restaurants/verify`

| Campo | Obrigatório | Descrição |
| --- | --- | --- |
| `restaurant_id` | Sim | O ID do Restaurante OpenTable (RID), um número como `1038007`. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "1038007" }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "1038007",
      "diningAreaCount": 2,
      "diningAreaNames": ["Main Room", "Garden"],
      "tableTypes": ["default", "outdoor", "bar"]
    }
  }
}
```

`verified: false` significa que nenhum restaurante OpenTable possui esse ID. Um `403` significa que o restaurante existe, mas ainda não conectou a integração da plataforma dentro do OpenTable. Limitado a 10 tentativas a cada 5 minutos por conta.

**Adicionar um restaurante OpenTable**

`POST /appointments/opentable-restaurants`

| Campo | Obrigatório | Descrição |
| --- | --- | --- |
| `restaurant_id` | Sim | O ID do Restaurante verificado. |
| `restaurant_name` | Sim | Nome de exibição (um rótulo; também como a IA chama o restaurante). |
| `website_url` | Não | Uma URL http(s) exibida aos convidados quando a IA os encaminha para o restaurante. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "1038007", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
```

**Resposta** (`201 Created`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

**Atualizar um restaurante OpenTable salvo**

`PUT /appointments/opentable-restaurants/{restaurantId}`

Envie qualquer um de `restaurant_name`, `is_active` (pause com `false`) ou `website_url` (uma string vazia limpa o campo); campos omitidos permanecem inalterados.

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

**Remover um restaurante OpenTable**

`DELETE /appointments/opentable-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "1038007" } }`

Um `restaurantId` que não está atualmente na conta retorna `404` na atualização.

### TheFork

Os restaurantes do TheFork são acessados por meio das credenciais de parceiro do TheFork da plataforma, e o TheFork permite que essas credenciais vejam apenas os restaurantes que têm o parceiro da plataforma habilitado em suas contas do TheFork. Portanto, assim como no Formitable e no OpenTable, uma chamada de verificação é suficiente: um ID de restaurante acessível prova tanto que o restaurante existe quanto que o parceiro está habilitado nele. O ID é o UUID que o TheFork fornece ao restaurante no TheFork Manager, enviado como uma string. Até que o TheFork tenha aprovado a plataforma como parceira e emitido as credenciais, a chamada de verificação responde `503` — veja [TheFork](../integrations/thefork.md) para saber o que isso significa hoje.

**Verificar um restaurante do TheFork**

`POST /appointments/thefork-restaurants/verify`

| Campo | Obrigatório | Descrição |
| --- | --- | --- |
| `restaurant_id` | Sim | O ID do restaurante no TheFork, um UUID como `9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60`. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants/verify?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "verified": true,
    "restaurantDetails": {
      "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60",
      "partySizes": [1, 2, 3, 4, 5, 6, 7, 8]
    }
  }
}
```

`partySizes` são os tamanhos de grupo que o restaurante aceita online nos próximos 30 dias. Um `404` ou `403` significa que o TheFork não nos forneceu um restaurante com esse ID — ou o ID está incorreto, ou o parceiro da plataforma ainda não está habilitado nesse restaurante; um `400` significa que o ID não é um UUID. Limitado a 10 tentativas a cada 5 minutos por conta.

**Adicionar um restaurante do TheFork**

`POST /appointments/thefork-restaurants`

| Campo | Obrigatório | Descrição |
| --- | --- | --- |
| `restaurant_id` | Sim | O ID do restaurante verificado (UUID). |
| `restaurant_name` | Sim | Nome de exibição (um rótulo; também como a IA chama o restaurante). |
| `website_url` | Não | Uma URL http(s) exibida aos convidados quando a IA os encaminha para o restaurante. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
```

**Resposta** (`201 Created`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

**Atualizar um restaurante do TheFork salvo**

`PUT /appointments/thefork-restaurants/{restaurantId}`

Envie qualquer um de `restaurant_name`, `is_active` (pause com `false`) ou `website_url` (uma string vazia limpa o campo); campos omitidos permanecem inalterados.

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

**Remover um restaurante TheFork**

`DELETE /appointments/thefork-restaurants/{restaurantId}`

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`): `{ "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }`

Um `restaurantId` que não está atualmente na conta retorna `404` ao atualizar ou excluir.

### Trafft

O Trafft é conectado uma vez para toda a conta, não por local: um endereço da empresa mais as credenciais da API do painel administrativo do Trafft (**Features & Integrations → API & Connectors**, parte do plano Business do Trafft). A chamada de conexão verifica essas credenciais no Trafft antes de armazenar qualquer coisa, portanto, um endereço incorreto, credenciais erradas ou um plano sem acesso à API falhará aqui em vez de durante uma conversa com o cliente. O segredo do cliente (client secret) é armazenado criptografado e nunca é retornado por nenhum endpoint.

Todas as três chamadas de escrita (`POST`, `PUT`, `DELETE`) exigem a permissão de **edição** de Integrações; o status `GET` requer a permissão de **visualização** de Integrações.

**Conectar Trafft**

`POST /appointments/trafft/connect`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `subdomain` | Sim | O endereço da empresa — a parte antes de `.admin.trafft.com` na URL em que você faz login, por exemplo, `acme`. Um endereço completo é aceito e reduzido ao mesmo valor. |
| `client_id` | Sim | Client ID da página API & Connectors do Trafft. |
| `client_secret` | Sim | Client Secret da mesma página. Armazenado criptografado, nunca retornado. |
| `company_name` | Não | Um rótulo para sua própria lista. |

```bash
curl -X POST "https://api.dmchamp.com/v1/appointments/trafft/connect?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subdomain": "acme",
    "client_id": "YOUR_TRAFFT_CLIENT_ID",
    "client_secret": "YOUR_TRAFFT_CLIENT_SECRET",
    "company_name": "Acme Salon"
  }'
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "subdomain": "acme",
    "service_count": 12,
    "employee_count": 4,
    "location_count": 2
  }
}
```

As contagens retornam do Trafft durante a verificação — elas são a maneira mais rápida de confirmar se as credenciais apontam para a conta que você pretendia.

**Obter o status da conexão**

`GET /appointments/trafft`

```bash
curl "https://api.dmchamp.com/v1/appointments/trafft" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`):

```json
{
  "success": true,
  "data": {
    "connected": true,
    "subdomain": "acme",
    "hostname": "acme.admin.trafft.com",
    "client_id": "YOUR_TRAFFT_CLIENT_ID",
    "company_name": "Acme Salon",
    "is_active": true,
    "service_count": 12,
    "employee_count": 4,
    "location_count": 2
  }
}
```

`connected: false` significa que nada foi configurado ainda. O segredo do cliente nunca é incluído nesta resposta.

**Atualizar a conexão**

`PUT /appointments/trafft`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `is_active` | Não | Defina `false` como pausado — a IA para de agendar no Trafft, a conexão permanece. `true` a retoma. |
| `company_name` | Não | Novo rótulo. |

```bash
curl -X PUT "https://api.dmchamp.com/v1/appointments/trafft" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

**Resposta** (`200 OK`): o mesmo objeto de conexão que o `GET` acima.

**Desconectar Trafft**

`DELETE /appointments/trafft`

```bash
curl -X DELETE "https://api.dmchamp.com/v1/appointments/trafft" \
  -H "X-API-Key: YOUR_API_KEY"
```

**Resposta** (`200 OK`): `{ "success": true }`

A desconexão remove apenas as credenciais armazenadas. Os agendamentos já existentes no Trafft permanecem inalterados.

> **Formato de erro em todos os endpoints Zenchef/Formitable/OpenTable/TheFork:** ao contrário do restante desta página, os erros aqui contêm o status duas vezes — uma como status HTTP e outra como `error_code` no corpo — por exemplo `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Trate-o da mesma forma que qualquer outro erro: verifique `success`, leia `error` para a mensagem.

---

## Erros da API de Agendamentos

Os endpoints de agendamento retornam o envelope de erro padrão:

```json
{
  "success": false,
  "error": "Appointment not found"
}
```

| Status | Quando ocorre em um endpoint de agendamento |
|---|---|
| `400` | Um campo obrigatório está faltando ou é inválido — por exemplo, um `start_time` incorreto, um `end_time` que não é posterior a `start_time`, uma combinação de filtros inválida, nenhum campo para atualizar ou um agendamento já cancelado. |
| `404` | O agendamento, contato ou tipo de evento não foi encontrado. |
| `409` | O intervalo de tempo solicitado já está ocupado (conflito de agendamento). |

Os códigos compartilhados que todo endpoint pode retornar — `401`, `403` (seu plano não inclui acesso à API), `429` (limite de taxa) e `500` — estão listados com orientações de nova tentativa em [Erros e Paginação](errors-and-pagination.md).

---

::: master-only
## Use seu próprio cliente Google OAuth (tela de consentimento do Agenda)

Quando uma conta conecta o Google Agenda, a janela de login do Google nomeia o projeto do cliente OAuth — por padrão, o da plataforma. Uma agência pode registrar seu próprio cliente Google OAuth 2.0 na conta da agência; a partir de então, a conexão de agenda para essa conta e cada subconta vinculada a ela é executada por meio desse cliente, para que a tela de consentimento mostre o nome e o logotipo da agência. Nada mais muda: o fluxo de conexão, a sincronização bidirecional e os endpoints de agendamento acima funcionam exatamente como antes.

> **Apenas Google Agenda.** O OAuth da caixa de entrada do Gmail para o canal de E-mail não é afetado.

### O que seu cliente precisa primeiro

1. **Um cliente OAuth 2.0** do tipo Aplicativo da Web em seu projeto do Google Cloud, com a **API do Google Agenda** ativada nesse projeto.
2. **Cada entrada `redirect_uris`** (retornada pelos endpoints abaixo) adicionada em URIs de redirecionamento autorizados do cliente. A primeira entrada é seu domínio `api.` verificado quando você tiver um — o Google só verifica uma marca cujo redirecionamento reside em um domínio que você possui — seguido pelo host neutro da plataforma como o fallback usado até então.
3. **A tela de consentimento** com sua marca, seu domínio em Domínios autorizados e os dois escopos do Agenda declarados (`scopes` na resposta). Até que o aplicativo seja publicado e verificado pelo Google, os usuários verão um aviso de aplicativo não verificado e o cliente terá um limite de 100 usuários.

### Salve seu cliente

`PUT /account-config/google-oauth-client`

| Campo | Obrigatório | Descrição |
|---|---|---|
| `client_id` | Sim | O ID do cliente OAuth 2.0, terminando em `.apps.googleusercontent.com`. |
| `client_secret` | Sim | O segredo do cliente. Verificado no Google antes de ser armazenado e, em seguida, criptografado. Nunca retornado por nenhum endpoint. |

```bash
curl -X PUT "https://api.dmchamp.com/v1/account-config/google-oauth-client?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
    "client_secret": "GOCSPX-your-client-secret"
  }'
```

**Resposta**

```json
{
  "success": true,
  "configured": true,
  "client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
  "redirect_uris": [
    "https://api.youragency.com/v1/auth-google-callback",
    "https://api.dmchamp.com/v1/auth-google-callback"
  ],
  "scopes": [
    "https://www.googleapis.com/auth/calendar.events",
    "https://www.googleapis.com/auth/calendar.readonly"
  ],
  "setup": ["…"]
}
```

Um segredo incorreto ou um ID de cliente desconhecido é recusado com `400` e o motivo do próprio Google em `error`, e nada é armazenado.

### Leia ou remova-o

`GET /account-config/google-oauth-client` retorna o mesmo resumo a qualquer momento — `configured: false` mais o `redirect_uris` e `scopes` antes que qualquer coisa seja salva, para que você possa configurar o lado do Google primeiro. `DELETE /account-config/google-oauth-client` remove o cliente: novas conexões revertem para o cliente da plataforma, e calendários que foram conectados através do cliente removido devem ser reconectados, porque apenas o cliente que emitiu uma conexão pode atualizá-la.

Os membros da equipe precisam de **Integrations: view** para `GET` e **Integrations: edit** para `PUT` / `DELETE`.
:::

---

## Próximos passos

- [Contatos](contacts.md) — crie e consulte os contatos para os quais você faz reservas.
- [Mensagens e Conversas](messages.md) — envie uma confirmação ou lembrete a um contato.
- [Webhooks](webhooks.md) — receba notificações quando agendamentos forem alterados.
