
# Afspraken

Met de Appointments API kun je afspraken boeken voor je contacten op basis van je eventtypes, en deze vervolgens ophalen, weergeven, bijwerken, annuleren of verwijderen. Het beantwoordt ook de vraag die in de meeste boekingsstromen als eerste komt — welke tijden zijn daadwerkelijk vrij — en dekt de agendakant: het weergeven van de Google-agenda's die je hebt gekoppeld en het importeren van afspraken die daar al in staan. Wanneer een Google-agenda-koppeling actief is, wordt de bijbehorende agenda-afspraak automatisch op de achtergrond aangemaakt en gesynchroniseerd. Restaurants die Zenchef of Formitable gebruiken voor hun eigen reserveringssysteem kunnen hier ook worden geverifieerd en gekoppeld, zodat de AI Agent echte tafels boekt in plaats van interne afspraken.

Alle paden op deze pagina zijn relatief ten opzichte van de basis-URL `https://api.youraiconnector.com/v1`. Voor elk verzoek is je API-sleutel vereist — zie [Authenticatie](authentication.md) voor de volledige lijst met manieren om deze te verzenden. De onderstaande voorbeelden gebruiken de `X-API-Key`-header, waarbij één cURL-voorbeeld ook de `?apiKey=`-queryvorm laat zien.

> **Gebeurtenissen versus afspraken:** Een *gebeurtenistype* is een definitie van een boekbaar tijdslot (het soort vergadering, de duur, de ruimtes). Een *afspraak* is één geboekte instantie van een gebeurtenistype voor een specifiek contact. Je boekt een afspraak door te verwijzen naar het contact en het gebeurtenistype.

---

## Het afspraakobject

Elk eindpunt dat een afspraak retourneert, gebruikt dezelfde structuur:

| Veld | Beschrijving |
|---|---|
| `id` | Unieke ID van de afspraak. |
| `contact_id` | ID van het contact waarmee de afspraak is geboekt. |
| `event_id` | ID van het gebeurtenistype waarop de afspraak is geboekt. |
| `status` | `Confirmed` of `Canceled`. |
| `start_time` | Starttijd van de afspraak, ISO 8601 in UTC. |
| `end_time` | Eindtijd van de afspraak, ISO 8601 in UTC. |
| `created_at` | Wanneer de afspraak is aangemaakt. |
| `last_modified_at` | Wanneer de afspraak voor het laatst is gewijzigd. |
| `room_name` | Ruimte of bron waarin de afspraak is geboekt, wanneer het gebeurtenistype gebruikmaakt van ruimtes. |
| `description` | Vrije beschrijving van de afspraak. |
| `summary` | Korte samenvatting of titel. |
| `cancelation_reason` | Reden opgegeven bij het annuleren van de afspraak, indien van toepassing. |
| `google_calendar_event_id` | ID van de gekoppelde Google Agenda-gebeurtenis. Wordt ingesteld zodra de agendasynchronisatie is voltooid; `null` wanneer er geen agenda is gekoppeld of terwijl de synchronisatie nog bezig is. |
| `calendar_synced` | `true` zodra de afspraak is gekoppeld aan een agenda-gebeurtenis. |
| `imported` | `true` wanneer de afspraak is geïmporteerd vanuit een externe agenda in plaats van direct geboekt. |
| `is_recurring` | `true` wanneer de afspraak deel uitmaakt van een terugkerende reeks. |
| `recurrence_frequency` | Hoe vaak de afspraak zich herhaalt, indien terugkerend. |
| `recurring_event_id` | ID van de terugkerende reeks waartoe deze afspraak behoort. |
| `recurring_interval` | Interval tussen herhalingen, indien terugkerend. |
| `recurring_sequence` | Positie van deze afspraak binnen de terugkerende reeks. |
| `end_after_x_occurrences` | Aantal voorkomens waarna de terugkerende reeks eindigt. |
| `booking_provider` | Bronsysteem waar de boeking vandaan komt, indien geboekt via een gekoppelde reserveringsaanbieder. |

> **Over agendasynchronisatie:** Direct nadat je een afspraak hebt geboekt of gewijzigd, kan `google_calendar_event_id` nog `null` zijn en kan `calendar_synced` `false` zijn, omdat de synchronisatie een moment later op de achtergrond wordt uitgevoerd. Haal de afspraak kort daarna opnieuw op om de ingevulde agendavelden te zien.

---

## Beschikbare tijdsloten vinden

`GET /appointments/available-slots`

Geeft de tijden terug die daadwerkelijk vrij zijn voor een eventtype tussen twee momenten. Dit is normaal gesproken de **eerste** aanroep in een boekingsstroom: toon deze tijdsloten, laat de persoon er een kiezen en verstuur vervolgens de gekozen tijd naar [Een afspraak boeken](#book-an-appointment).

Het antwoord houdt al rekening met de openingstijden en de lengte van het tijdslot van het eventtype zelf, de ruimtes, afspraken die je er al op hebt geboekt en alles wat geblokkeerd is in de gekoppelde Google-agenda's — dus een tijdslot dat hier wordt teruggegeven, is een tijdslot dat je kunt boeken.

| Query-parameter | Vereist | Beschrijving |
|---|---|---|
| `event_id` | Ja | Het eventtype om te controleren. Moet bij jouw account horen. |
| `start_time` | Ja | Begin van het venster waarvoor je tijdsloten wilt, ISO 8601 datum-tijd. |
| `end_time` | Ja | Einde van het venster, ISO 8601 datum-tijd. De gehele einddag is inbegrepen. |

De resultaten worden gegroepeerd per dag teruggegeven — en, wanneer het eventtype gebruikmaakt van ruimtes, één groep per ruimte per dag:

| Veld | Beschrijving |
|---|---|
| `date` | De dag die de groep beslaat, geschreven als `DD/MM/YYYY`. |
| `day` | Naam van de weekdag in kleine letters, bijvoorbeeld `monday`. |
| `room_name` | De ruimte of bron waar deze groep bij hoort, wanneer het eventtype gebruikmaakt van ruimtes. |
| `available_slots` | De boekbare blokken op die dag, vroegste eerst. |

Elk item in `available_slots` bevat:

| Veld | Beschrijving |
|---|---|
| `start_time` | Begin van het blok als `HH:mm`. |
| `end_time` | Einde van het blok als `HH:mm`. |
| `available` | `true` — alleen vrije tijd wordt geretourneerd. |
| `spots_left` | Hoeveel boekingen er nog in dit blok passen. Alleen aanwezig bij eventtypes die meer dan één boeking per tijdslot toestaan. |

> **Tijden zijn lokaal voor het eventtype, niet UTC.** `date`, `start_time` en `end_time` zijn kloktijden in de eigen tijdzone van het eventtype (de overschrijving ervan, of de tijdzone van je account als er geen is). [Een afspraak boeken](#book-an-appointment) verwacht een ISO 8601 UTC-moment, dus converteer het gekozen tijdslot voordat je het verstuurt.

**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"])
```

**Antwoord** (`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 }
      ]
    }
  ]
}
```

Een dag waarop niets vrij is, verschijnt simpelweg niet. Het ontbreken van `event_id`, `start_time` of `end_time` resulteert in `400`; een eventtype dat niet bij jouw account hoort, resulteert in `404`.

---

## Een afspraak boeken

`POST /appointments`

Boekt een nieuwe afspraak voor een contact op een van je gebeurtenistypen. De eindtijd wordt automatisch berekend op basis van de duur van het tijdslot van het gebeurtenistype.

De boeking wordt gecontroleerd op conflicten: als het aangevraagde tijdslot overlapt met een bestaande bevestigde afspraak voor hetzelfde gebeurtenistype, mislukt het verzoek met een `409` en wordt er niets aangemaakt.

| Veld | Vereist | Beschrijving |
|---|---|---|
| `contact_id` | Ja | ID van het contact waarvoor geboekt moet worden. Moet tot je account behoren. |
| `event_id` | Ja | ID van het gebeurtenistype waarop geboekt moet worden. Moet tot je account behoren. |
| `start_time` | Ja | Gewenste starttijd als ISO 8601 datum-tijd. |
| `room_name` | Nee | Naam van de ruimte of bron, wanneer het gebeurtenistype gebruikmaakt van ruimtes. |

**cURL** (met gebruik van de `?apiKey=`-queryvorm)

```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"])
```

**Antwoord** (`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
  }
}
```

---

## Een afspraak ophalen

`GET /appointments/{appointmentId}`

Geeft één afspraak terug op basis van het ID, inclusief de synchronisatiestatus met de agenda.

**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"])
```

**Antwoord** (`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
  }
}
```

---

## Afspraken weergeven

`GET /appointments`

Geeft een lijst met afspraken voor uw account, beginnend bij de nieuwste, met cursor-gebaseerde paginering.

| Query-parameter | Verplicht | Beschrijving |
|---|---|---|
| `contact_id` | Nee | Retourneer alleen afspraken voor dit contact. Met contact gefilterde lijsten bevatten **alleen bevestigde afspraken** |
| `date` | Nee | Retourneer alleen afspraken op deze kalenderdag (`YYYY-MM-DD`). **Vereist `contact_id`.** |
| `status` | Nee | Filter op `Confirmed` of `Canceled`. Alleen beschikbaar **zonder** `contact_id`. |
| `limit` | Nee | Paginagrootte, een geheel getal tussen 1 en 100. Standaard `50`. |
| `cursor` | Nee | De `next_cursor`-waarde van een vorig antwoord. |

Een paar regels om rekening mee te houden:

- **Zonder filters** krijgt u elke afspraak in het account, pagina voor pagina.
- **Op contact** — stel `contact_id` in om de bevestigde afspraken van één contact te zien. U kunt dit beperken tot één dag door ook `date` mee te geven.
- **Op status** — stel `status` in (zonder `contact_id`) om alleen `Confirmed` of alleen `Canceled` afspraken in het hele account weer te geven.
- Het `date`-filter zonder `contact_id`, of `status=Canceled` samen met `contact_id`, retourneert een `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"])
```

**Antwoord** (`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
}
```

Om door de resultaten te bladeren, geeft u de `next_cursor` van het ene antwoord door als de `cursor` van het volgende verzoek. Ga door totdat `next_cursor` gelijk is aan `null`. Zie [Fouten & Paginering](errors-and-pagination.md) voor het gedeelde pagineringspatroon.

---

## Een afspraak bijwerken

`PUT /appointments/{appointmentId}`

Plan een afspraak opnieuw in of wijzig de details. Stuur alleen de velden die u wilt wijzigen — er is er minimaal één vereist. De gecombineerde start- en eindtijd moeten in chronologische volgorde blijven (`end_time` moet na `start_time` vallen). Wijzigingen worden automatisch gesynchroniseerd met het gekoppelde agendagebeurtenis.

| Veld | Beschrijving |
|---|---|
| `start_time` | Nieuwe starttijd, ISO 8601 datum-tijd. |
| `end_time` | Nieuwe eindtijd, ISO 8601 datum-tijd. Moet na de starttijd liggen. |
| `room_name` | Nieuwe naam voor de ruimte of resource. |
| `description` | Nieuwe beschrijving, of `null` om deze te wissen. |
| `summary` | Nieuwe samenvatting, of `null` om deze te wissen. |

**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"])
```

**Antwoord** (`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
  }
}
```

---

## Een afspraak annuleren

`POST /appointments/{appointmentId}/cancel`

Annuleert een bevestigde afspraak, waarbij optioneel een reden kan worden opgegeven. De afspraak blijft in uw account staan met de status `Canceled` en de gekoppelde agendagebeurtenis wordt automatisch op de achtergrond verwijderd. Het annuleren van een reeds geannuleerde afspraak resulteert in een `400`.

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `cancellation_reason` | Nee | Reden voor de annulering, opgeslagen bij de afspraak. |

**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"])
```

**Antwoord** (`200 OK`):

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

---

## Een afspraak verwijderen

`DELETE /appointments/{appointmentId}`

Verwijdert een afspraak en de bijbehorende verwijzingen definitief. Als u de boeking alleen wilt afzeggen maar het record wilt behouden, gebruik dan in plaats daarvan [annuleren](#cancel-an-appointment).

**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"])
```

**Antwoord** (`200 OK`):

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

---

## Je gekoppelde Google-agenda's weergeven

`GET /appointments/google-calendars`

Geeft de Google-agenda's terug die beschikbaar zijn voor dit account, rechtstreeks vanuit Google — handig om de accounthouder een keuze te laten maken uit welke agenda hieronder geïmporteerd moet worden, of gewoon om te bevestigen dat de koppeling actief is.

Dit werkt pas zodra het account Google Calendar heeft gekoppeld (Instellingen → Integraties) met ten minste leestoegang. Als dit niet het geval is, of als de verleende toegang niet langer de calendar-read scope bevat, ontvang je een `400` waarin wordt gevraagd deze te (her)koppelen.

**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"])
```

**Antwoord** (`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"
    }
  ]
}
```

Elk item heeft de vorm van Google's eigen [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList), dus veldnamen volgen Google's `camelCase`, niet de gebruikelijke `snake_case` van deze API — dat zijn Google's gegevens die ongewijzigd worden doorgegeven, niet de onze. Een ontbrekende of ingetrokken koppeling retourneert `400` met een foutmelding die uitlegt dat Google Calendar moet worden (her)gekoppeld.

---

## Evenementen importeren uit een Google Calendar

`POST /appointments/import-calendar-events`

Haalt de evenementen op die al in de gekoppelde Google Calendar(s) van een campagne of AI-agent staan en zet deze om in afspraken — handig de eerste keer dat je een agenda koppelt waar al boekingen in staan. Dit kan even duren (elk evenement wordt geëxtraheerd om te achterhalen voor wie het is), dus het wordt nooit inline uitgevoerd: het verzoek plaatst een achtergrondtaak in de wachtrij en geeft je een `job_id` terug om te pollen.

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `campaign_id` | Eén van deze twee | De campagne waarvan de gekoppelde agenda('s) moeten worden geïmporteerd. |
| `agent_id` | Eén van deze twee | De AI-agent waarvan de gekoppelde agenda('s) moeten worden geïmporteerd. |
| `identifier` | Ja | `"EMAIL"` of `"PHONE_NUMBER"` — welk contactgegeven uit elk agenda-evenement moet worden geëxtraheerd om de bijbehorende contactpersoon te matchen of aan te maken. |

Stuur precies één van `campaign_id` / `agent_id`, nooit beide en nooit geen van beide — elke andere combinatie retourneert een `400`. Degene die je verstuurt, moet bij je account horen, anders krijg je een `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"])
```

**Antwoord** (`202 Accepted`):

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

`campaign_id` en `agent_id` echoën terug welke je hebt verstuurd; de andere is altijd `null`.

### De importtaak pollen

`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"
```

**Antwoord** (`200 OK`):

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

| `status` | Betekenis |
|---|---|
| `queued` | Nog niet opgepakt. Blijf pollen. |
| `processing` | De import is bezig. Blijf pollen. |
| `completed` | Klaar — `message` bevat een korte, leesbare samenvatting. |
| `failed` | Er is iets misgegaan — `error` bevat de reden. |

`GET` op een `jobId` die niet bestaat (of bij een ander account hoort) retourneert `404`.

---

## Restaurantboekingsintegraties (Zenchef / Formitable)

Zenchef en Formitable zijn restaurantreserveringssystemen waar je AI-agent echte tafels via kan boeken. Elk heeft een **openbare, niet-geauthenticeerde boekingswidget** (`https://api.youraiconnector.com/v1/zenchef-widget/...` en `https://api.youraiconnector.com/v1/formitable-widget/...`) die in de chat voor de gast wordt weergegeven — die widget-routes zijn gewone HTML-pagina's die bedoeld zijn om in een browser te worden geopend, geen JSON API-eindpunten, dus ze worden hier niet gedocumenteerd. Wat volgt zijn de eindpunten voor accountbeheer: verifiëren of een restaurant-ID bij de accounthouder hoort, en het vervolgens toevoegen, bijwerken of verwijderen ervan.

### Zenchef

Het koppelen van een Zenchef-restaurant is een verificatie in twee stappen, zodat de accounthouder bewijst dat hij het restaurant daadwerkelijk beheert voordat het aan de bot wordt gekoppeld: controleer eerst of het ID bestaat (zonder de naam te onthullen), en laat ze vervolgens zelf de naam van het restaurant typen en verifieer of deze overeenkomt.

**Stap 1 — Controleren of een restaurant-ID bestaat**

`POST /appointments/zenchef-restaurants/check`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_id` | Ja | Het Zenchef-restaurant-ID om te controleren. |

```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" }'
```

**Antwoord** (`200 OK`):

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

`exists: false` betekent dat geen enkel Zenchef-restaurant dat ID heeft — er is niets meer te doen. Snelheidslimiet van 10 controles per 5 minuten per account; overschrijding resulteert in `429`.

**Stap 2 — De naam van het restaurant verifiëren**

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

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_id` | Ja | Het Zenchef-restaurant-ID uit stap 1. |
| `user_input_name` | Ja | De naam die de accounthouder heeft ingetypt — vergeleken met de echte naam van het restaurant op Zenchef (ongevoelig voor hoofdletters/spaties). |

```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" }'
```

**Antwoord** (`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` betekent dat de naam niet overeenkwam — `restaurantDetails` wordt weggelaten, vraag de accounthouder om het opnieuw te proberen. Snelheidslimiet van 3 pogingen per 5 minuten (strenger dan de bestaancontrole, aangezien dit de daadwerkelijke verificatiestap is). Een `restaurant_id` die niet langer wordt opgelost op Zenchef resulteert in `404`.

**Stap 3 — Het restaurant opslaan**

`POST /appointments/zenchef-restaurants`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_id` | Ja | 1–64 tekens, letters/cijfers/underscore/koppelteken. |
| `restaurant_name` | Ja | De geverifieerde restaurantnaam uit stap 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" }'
```

**Antwoord** (`201 Created`):

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

**Een opgeslagen Zenchef-restaurant bijwerken**

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

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_name` | Nee | Nieuwe weergavenaam. |
| `is_active` | Nee | Stel `false` in om te voorkomen dat de bot bij dit restaurant boekt zonder het te verwijderen. |

```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 }'
```

**Antwoord** (`200 OK`): dezelfde vorm als het antwoord bij opslaan hierboven.

**Een Zenchef-restaurant verwijderen**

`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"
```

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

Een `restaurantId` die momenteel niet aan het account is gekoppeld, geeft `404` terug bij een update of verwijdering.

### Formitable

Formitable heeft niet de tweestaps-naamcontrole nodig die Zenchef wel vereist — de restaurant-ID's zijn al per bedrijf gescopeerd, dus één verificatie-aanroep is voldoende. Het heeft ook een details-opzoekfunctie die wordt gebruikt om de website-URL van het restaurant tijdens de configuratie in de cache op te slaan.

**Een restaurant-ID verifiëren**

`POST /appointments/formitable-restaurants/verify`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_id` | Ja | Het Formitable restaurant-ID. |
| `language` | Nee | Taaltag voor het probe-verzoek. Standaard ingesteld op `"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" }'
```

**Antwoord** (`200 OK`):

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

Een `restaurant_id` die Formitable niet herkent, retourneert `404`. Snelheidslimiet van 10 pogingen per 5 minuten per account.

**Restaurantdetails ophalen**

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

Haalt het openbare profiel van het restaurant op uit Formitable, inclusief de website — wordt gebruikt om de website-URL in de cache op te slaan tijdens het instellen van het restaurant. `language` is een optionele queryparameter, die standaard op `"en"` staat.

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

**Antwoord** (`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"
  }
}
```

**Sla het restaurant op**

`POST /appointments/formitable-restaurants`

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_id` | Ja | 1–64 tekens, letters/cijfers/underscore/koppelteken. |
| `restaurant_name` | Ja | Weergavenaam. |
| `language` | Ja | ISO-taallabel, bijv. `"en"` of `"en-GB"`. |
| `website_url` | Nee | De website van het restaurant, uit de bovenstaande details-opzoeking. Moet `http(s)://` zijn. |

```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"
  }'
```

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

**Update een opgeslagen Formitable-restaurant**

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

| Veld | Verplicht | Beschrijving |
|---|---|---|
| `restaurant_name` | Nee | Nieuwe weergavenaam. |
| `language` | Nee | Nieuw ISO-taallabel. |
| `is_active` | Nee | Stel `false` in om te voorkomen dat de bot boekingen maakt voor dit restaurant zonder het te verwijderen. |
| `website_url` | Nee | Nieuwe website-URL. |

```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 }'
```

**Antwoord** (`200 OK`): dezelfde vorm als het antwoord bij opslaan hierboven.

**Verwijder een Formitable-restaurant**

`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"
```

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

Een `restaurantId` die momenteel niet aan het account is gekoppeld, geeft `404` terug bij een update of verwijdering.

> **Foutvorm op alle Zenchef/Formitable-endpoints:** in tegenstelling tot de rest van deze pagina, bevatten fouten hier hun status tweemaal — eenmaal als de HTTP-status en eenmaal als `error_code` in de body — bijvoorbeeld `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Handel dit op dezelfde manier af als elke andere fout: controleer `success`, lees `error` voor het bericht.

---

## Fouten in de Appointments API

Appointment-endpoints retourneren de standaardfouten-envelop:

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

| Status | Wanneer dit gebeurt op een appointment-endpoint |
|---|---|
| `400` | Een verplicht veld ontbreekt of is ongeldig — bijvoorbeeld een onjuiste `start_time`, een `end_time` die niet na `start_time` ligt, een ongeldige filtercombinatie, geen velden om bij te werken, of een reeds geannuleerde afspraak. |
| `404` | De afspraak, het contact of het type evenement is niet gevonden. |
| `409` | Het gevraagde tijdslot is al bezet (boekingsconflict). |

De gedeelde codes die elk endpoint kan retourneren — `401`, `403` (uw abonnement bevat geen API-toegang), `429` (snelheidslimiet) en `500` — worden vermeld met richtlijnen voor opnieuw proberen in [Fouten & Paginering](errors-and-pagination.md).

---

## Volgende stappen

- [Contacten](contacts.md) — maak contacten aan en zoek ze op voor wie u boekt.
- [Berichten & Gesprekken](messages.md) — stuur een contactpersoon een bevestiging of herinnering.
- [Webhooks](webhooks.md) — ontvang een melding wanneer afspraken wijzigen.
