
# Marcações

A API de Marcações permite-lhe marcar compromissos para os seus contactos nos seus tipos de evento, e depois obter, listar, atualizar, cancelar ou eliminá-los. Também responde à pergunta que surge primeiro na maioria dos fluxos de marcação — que horários estão realmente livres — e cobre a parte do calendário: listar os Google Calendars que tem ligados e importar eventos que já existem neles. Quando uma ligação ao Google Calendar está ativa, o evento de calendário correspondente é criado e mantido sincronizado automaticamente em segundo plano. Os restaurantes que utilizam o Zenchef ou o Formitable para o seu próprio sistema de reservas também podem ser verificados e ligados aqui, para que o Agente de IA reserve mesas reais em vez de marcações internas.

Todos os caminhos nesta página são relativos ao URL base `https://api.youraiconnector.com/v1`. Cada pedido necessita da sua chave de API — consulte [Autenticação](authentication.md) para obter a lista completa de formas de a enviar. Os exemplos abaixo utilizam o cabeçalho `X-API-Key`, com um exemplo cURL que mostra também o formulário de consulta `?apiKey=`.

> **Eventos vs. marcações:** Um *tipo de evento* é uma definição de espaço reservável (o tipo de reunião, a sua duração, as suas salas). Uma *marcação* é uma instância reservada de um tipo de evento para um contacto específico. Reserva uma marcação referenciando o contacto e o tipo de evento.

---

## O objeto de marcação

Cada endpoint que devolve uma marcação utiliza a mesma estrutura:

| Campo | Descrição |
|---|---|
| `id` | ID único da marcação. |
| `contact_id` | ID do contacto com quem a marcação foi feita. |
| `event_id` | ID do tipo de evento em que a marcação foi feita. |
| `status` | `Confirmed` ou `Canceled`. |
| `start_time` | Início da marcação, ISO 8601 em UTC. |
| `end_time` | Fim da marcação, ISO 8601 em UTC. |
| `created_at` | Quando a marcação foi criada. |
| `last_modified_at` | Quando a marcação foi alterada pela última vez. |
| `room_name` | Sala ou recurso onde a marcação foi feita, quando o tipo de evento utiliza salas. |
| `description` | Descrição de formato livre da marcação. |
| `summary` | Resumo ou título curto. |
| `cancelation_reason` | Motivo fornecido quando a marcação foi cancelada, se aplicável. |
| `google_calendar_event_id` | ID do evento do Google Calendar associado. Definido assim que a sincronização do calendário termina; `null` quando nenhum calendário está ligado ou enquanto a sincronização ainda está em curso. |
| `calendar_synced` | `true` assim que a marcação estiver ligada a um evento de calendário. |
| `imported` | `true` quando a marcação foi importada de um calendário externo em vez de reservada diretamente. |
| `is_recurring` | `true` quando a marcação faz parte de uma série recorrente. |
| `recurrence_frequency` | Frequência de repetição da marcação, quando recorrente. |
| `recurring_event_id` | ID da série recorrente a que esta marcação pertence. |
| `recurring_interval` | Intervalo entre repetições, quando recorrente. |
| `recurring_sequence` | Posição desta marcação dentro da 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 através de um fornecedor de reservas ligado. |

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

---

## Encontrar horários disponíveis

`GET /appointments/available-slots`

Devolve os horários que estão genuinamente livres num tipo de evento entre dois momentos. Esta é normalmente a **primeira** chamada num fluxo de marcação: mostre estes horários, deixe a pessoa escolher um e, em seguida, envie a hora escolhida para [Marcar um compromisso](#book-an-appointment).

A resposta já tem em conta o horário de funcionamento e a duração dos intervalos do próprio tipo de evento, as suas salas, as marcações que já efetuou nele e tudo o que está bloqueado nos Google Calendars ligados — por isso, um horário que aparece aqui é um horário que pode marcar.

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

Os resultados são devolvidos agrupados por dia — e, quando o tipo de evento utiliza 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 minúsculas, por exemplo `monday`. |
| `room_name` | A sala ou recurso a que este grupo pertence, quando o tipo de evento utiliza salas. |
| `available_slots` | Os blocos marcáveis nesse dia, do mais cedo para o mais tarde. |

Cada entrada em `available_slots` tem:

| Campo | Descrição |
|---|---|
| `start_time` | Início do bloco como `HH:mm`. |
| `end_time` | Fim do bloco como `HH:mm`. |
| `available` | `true` — apenas é devolvido tempo livre. |
| `spots_left` | Quantas marcações ainda cabem neste bloco. Apenas presente em tipos de evento que aceitam mais do que uma marcação 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 (a sua substituição, ou o fuso horário da sua conta quando não tem nenhum). [Marcar um compromisso](#book-an-appointment) espera um instante UTC ISO 8601, por isso converta o horário que escolheu antes de o enviar.

**cURL**

```bash
curl "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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` devolve `400`; um tipo de evento que não está na sua conta devolve `404`.

---

## Reservar uma marcação

`POST /appointments`

Reserva uma nova marcação para um contacto num dos seus tipos de evento. A hora de fim é calculada automaticamente a partir da duração do espaço do tipo de evento.

A reserva é verificada quanto a conflitos: se o espaço solicitado se sobrepuser a uma marcação confirmada existente no mesmo tipo de evento, o pedido falha com um `409` e nada é criado.

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

**cURL** (utilizando o formulário de consulta `?apiKey=`)

```bash
curl -X POST "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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}`

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

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.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.youraiconnector.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 | Devolve apenas agendamentos para este contacto. As listagens filtradas por contacto incluem **apenas agendamentos confirmados** |
| `date` | Não | Devolve apenas agendamentos neste dia de calendário (`YYYY-MM-DD`). **Requer `contact_id`.** |
| `status` | Não | Filtrar 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. Predefinição `50`. |
| `cursor` | Não | O valor `next_cursor` de uma resposta anterior. |

Algumas regras a ter em conta:

- **Sem filtros**, obtém todos os agendamentos da conta, página a página.
- **Por contacto** — defina `contact_id` para ver os agendamentos confirmados de um contacto. Pode restringir a um único dia passando também `date`.
- **Por estado** — 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` juntamente com `contact_id`, devolve um `400`.

**cURL**

```bash
curl "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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 entre os resultados, passe o `next_cursor` de uma resposta como o `cursor` do pedido seguinte. Continue até que `next_cursor` seja `null`. Consulte [Erros e Paginação](errors-and-pagination.md) para o padrão de paginação partilhado.

---

## Atualizar um agendamento

`PUT /appointments/{appointmentId}`

Reagende um compromisso ou altere os seus detalhes. Envie apenas os campos que pretende alterar — é necessário pelo menos um. O início e o fim combinados devem manter-se por ordem cronológica (`end_time` deve ser posterior a `start_time`). As alterações são sincronizadas automaticamente com o evento do calendário associado.

| Campo | Descrição |
|---|---|
| `start_time` | Novo início, data-hora ISO 8601. |
| `end_time` | Novo fim, data-hora ISO 8601. Deve ser posterior à hora de início. |
| `room_name` | Novo nome da sala ou recurso. |
| `description` | Nova descrição, ou `null` para a limpar. |
| `summary` | Novo resumo, ou `null` para o limpar. |

**cURL**

```bash
curl -X PUT "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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, registando opcionalmente um motivo. O agendamento permanece na sua conta com o estado `Canceled` e o evento de calendário associado é removido automaticamente em segundo plano. O cancelamento de um agendamento já cancelado devolve um `400`.

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

**cURL**

```bash
curl -X POST "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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"
}
```

---

## Eliminar um agendamento

`DELETE /appointments/{appointmentId}`

Elimina permanentemente um agendamento e as suas referências. Se apenas pretende cancelar a marcação mantendo o registo, utilize [cancelar](#cancel-an-appointment) em vez disso.

**cURL**

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

**JavaScript**

```javascript
const res = await fetch(
  "https://api.youraiconnector.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.youraiconnector.com/v1/appointments/aBcD1234eFgH5678",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
```

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

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

---

## Listar os seus Google Calendars ligados

`GET /appointments/google-calendars`

Devolve 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 ligação está ativa.

Isto só funciona depois de a conta ter ligado o Google Calendar (Definições → Integrações) com, pelo menos, acesso de leitura. Se não o tiver feito, ou se o acesso concedido já não incluir o âmbito de leitura do calendário, receberá um `400` a indicar-lhe que deve ligá-lo (ou ligá-lo novamente).

**cURL**

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

**JavaScript**

```javascript
const res = await fetch("https://api.youraiconnector.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.youraiconnector.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 tem o formato [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) da própria Google, pelo que os nomes dos campos seguem a `camelCase` da Google e não a `snake_case` habitual desta API — são dados da Google transmitidos tal como estão, não os nossos. Uma ligação em falta ou revogada devolve um `400` com um erro a explicar que o Google Calendar precisa de ser ligado (ou ligado novamente).

---

## Importar eventos de um Google Calendar

`POST /appointments/import-calendar-events`

Extrai os eventos que já se encontram nos Google Calendar(s) ligados a uma campanha ou a um Agente de IA e transforma-os em marcações — útil na primeira vez que liga um calendário que já tem reservas. Isto pode demorar algum tempo (cada evento passa por um processo de extração para determinar a quem se destina), pelo que nunca é executado em linha: o pedido coloca em fila de espera um trabalho de fundo e devolve-lhe um `job_id` para consulta.

| Campo | Obrigatório | Descrição |
|---|---|---|
| `campaign_id` | Um destes dois | A campanha cujos calendários ligados devem ser importados. |
| `agent_id` | Um destes dois | O Agente de IA cujos calendários ligados devem ser importados. |
| `identifier` | Sim | `"EMAIL"` ou `"PHONE_NUMBER"` — que informação de contacto extrair de cada evento do calendário para corresponder ou criar o contacto a que pertence. |

Envie exatamente um de `campaign_id` / `agent_id`, nunca ambos e nunca nenhum — qualquer uma destas combinações devolve um `400`. O que enviar tem de pertencer à sua conta, caso contrário receberá um `404`.

**cURL**

```bash
curl -X POST "https://api.youraiconnector.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.youraiconnector.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.youraiconnector.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` devolvem o que enviou; o outro é sempre `null`.

### Consultar o trabalho de importação

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

```bash
curl "https://api.youraiconnector.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 processado. Continue a consultar. |
| `processing` | A importação está em curso. Continue a consultar. |
| `completed` | Concluído — `message` contém um breve resumo legível. |
| `failed` | Algo correu mal — `error` contém o motivo. |

`GET` num `jobId` que não existe (ou que pertence a uma conta diferente) devolve `404`.

---

## Integrações de reservas de restaurantes (Zenchef / Formitable)

O Zenchef e o Formitable são sistemas de reserva de restaurantes através dos quais o seu Agente de IA pode reservar mesas reais. Cada um tem um **widget de reserva público e não autenticado** (`https://api.youraiconnector.com/v1/zenchef-widget/...` e `https://api.youraiconnector.com/v1/formitable-widget/...`) que é apresentado no chat para o cliente — essas rotas de widget são páginas HTML simples destinadas a ser abertas num navegador, não pontos de extremidade de API JSON, pelo que não estão documentadas aqui. O que se segue são os pontos de extremidade de gestão de conta: verificar se um ID de restaurante pertence ao titular da conta e, em seguida, adicioná-lo, atualizá-lo ou removê-lo.

### Zenchef

A ligação de um restaurante Zenchef é um processo de verificação de dois passos, para que o titular da conta prove que realmente gere o restaurante antes de este ser ligado ao bot: primeiro, verifique se o ID existe (sem revelar o nome), depois peça-lhe para escrever o nome do restaurante e verifique se corresponde.

**Passo 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 verificar. |

```bash
curl -X POST "https://api.youraiconnector.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 tem esse ID — não há mais nada a fazer. Limitado a 10 verificações por cada 5 minutos por conta; exceder este limite devolve `429`.

**Passo 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 do passo 1. |
| `user_input_name` | Sim | O nome que o titular da conta escreveu — comparado com o nome real do restaurante no Zenchef (insensível a maiúsculas/minúsculas e espaços). |

```bash
curl -X POST "https://api.youraiconnector.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 para tentar novamente. Limitado a 3 tentativas por cada 5 minutos (mais restrito do que a verificação de existência, uma vez que este é o passo de prova real). Um `restaurant_id` que já não é resolvido no Zenchef devolve `404`.

**Passo 3 — Guardar o restaurante**

`POST /appointments/zenchef-restaurants`

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

```bash
curl -X POST "https://api.youraiconnector.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 guardado**

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

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_name` | Não | Novo nome de apresentação. |
| `is_active` | Não | Defina `false` para impedir que o bot efetue reservas neste restaurante sem o remover. |

```bash
curl -X PUT "https://api.youraiconnector.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`): mesma estrutura que a resposta de guardar acima.

**Remover um restaurante Zenchef**

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

```bash
curl -X DELETE "https://api.youraiconnector.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 esteja atualmente na conta devolve `404` ao atualizar ou eliminar.

### Formitable

O Formitable não necessita da prova de nome em dois passos como o Zenchef — os seus IDs de restaurante já estão delimitados por empresa, pelo que uma chamada de verificação é suficiente. Também possui uma consulta de detalhes utilizada para colocar em cache o URL do website 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 de restaurante Formitable. |
| `language` | Não | Etiqueta de idioma para o pedido de sondagem. O valor predefinido é `"nl"`. |

```bash
curl -X POST "https://api.youraiconnector.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 reconheça devolve `404`. Limitado a 10 tentativas por cada 5 minutos por conta.

**Obter detalhes do restaurante**

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

Obtém o perfil público do restaurante a partir do Formitable, incluindo o seu website — utilizado para colocar em cache o URL do website durante a configuração do restaurante. `language` é um parâmetro de consulta opcional, com o valor predefinido `"en"`.

```bash
curl "https://api.youraiconnector.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"
  }
}
```

**Guardar o restaurante**

`POST /appointments/formitable-restaurants`

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

```bash
curl -X POST "https://api.youraiconnector.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 guardado**

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

| Campo | Obrigatório | Descrição |
|---|---|---|
| `restaurant_name` | Não | Novo nome a apresentar. |
| `language` | Não | Nova etiqueta de idioma ISO. |
| `is_active` | Não | Defina `false` para impedir que o bot efetue reservas neste restaurante sem o remover. |
| `website_url` | Não | Novo URL do website. |

```bash
curl -X PUT "https://api.youraiconnector.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`): mesma estrutura que a resposta de guardar acima.

**Remover um restaurante Formitable**

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

```bash
curl -X DELETE "https://api.youraiconnector.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 esteja atualmente na conta devolve `404` ao atualizar ou eliminar.

> **Estrutura de erro em todos os endpoints Zenchef/Formitable:** ao contrário do resto desta página, os erros aqui apresentam o seu estado duas vezes — uma como estado 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 obter a mensagem.

---

## Erros da API de marcações

Os endpoints de marcações devolvem o envelope de erro padrão:

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

| Estado | Quando ocorre num endpoint de marcação |
|---|---|
| `400` | Falta um campo obrigatório ou este é inválido — por exemplo, um `start_time` incorreto, uma `end_time` que não é posterior a `start_time`, uma combinação de filtros inválida, ausência de campos para atualizar ou uma marcação já cancelada. |
| `404` | A marcação, o contacto ou o tipo de evento não foi encontrado. |
| `409` | O intervalo de tempo solicitado já está ocupado (conflito de agendamento). |

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

---

## Próximos passos

- [Contactos](contacts.md) — crie e procure os contactos para os quais efetua reservas.
- [Mensagens e Conversas](messages.md) — envie uma confirmação ou um lembrete a um contacto.
- [Webhooks](webhooks.md) — receba notificações quando os agendamentos forem alterados.
