
# Rendez-vous

L'API Appointments vous permet de réserver des rendez-vous pour vos contacts sur vos types d'événements, puis de les récupérer, les lister, les mettre à jour, les annuler ou les supprimer. Elle répond également à la question qui se pose en premier dans la plupart des flux de réservation — quels créneaux sont réellement libres — et couvre le volet calendrier : lister les agendas Google que vous avez connectés et importer les événements qui y figurent déjà. Lorsqu'une connexion à un agenda Google est active, l'événement correspondant est créé et synchronisé automatiquement en arrière-plan. Les restaurants utilisant Zenchef ou Formitable pour leur propre système de réservation peuvent également être vérifiés et connectés ici, afin que l'agent IA réserve de vraies tables au lieu de rendez-vous internes.

Tous les chemins sur cette page sont relatifs à l'URL de base `https://api.youraiconnector.com/v1`. Chaque requête nécessite votre clé API — consultez [Authentification](authentication.md) pour obtenir la liste complète des méthodes pour l'envoyer. Les exemples ci-dessous utilisent l'en-tête `X-API-Key`, avec un exemple cURL montrant également le formulaire de requête `?apiKey=`.

> **Événements vs rendez-vous :** Un *type d'événement* est une définition de créneau réservable (le type de réunion, sa durée, ses salles). Un *rendez-vous* est une instance réservée d'un type d'événement pour un contact spécifique. Vous réservez un rendez-vous en référençant le contact et le type d'événement.

---

## L'objet rendez-vous

Chaque point de terminaison qui renvoie un rendez-vous utilise la même structure :

| Champ | Description |
|---|---|
| `id` | ID unique du rendez-vous. |
| `contact_id` | ID du contact avec lequel le rendez-vous est pris. |
| `event_id` | ID du type d'événement sur lequel le rendez-vous a été réservé. |
| `status` | `Confirmed` ou `Canceled`. |
| `start_time` | Début du rendez-vous, au format ISO 8601 en UTC. |
| `end_time` | Fin du rendez-vous, au format ISO 8601 en UTC. |
| `created_at` | Date de création du rendez-vous. |
| `last_modified_at` | Date de la dernière modification du rendez-vous. |
| `room_name` | Salle ou ressource dans laquelle le rendez-vous est réservé, lorsque le type d'événement utilise des salles. |
| `description` | Description libre du rendez-vous. |
| `summary` | Résumé ou titre court. |
| `cancelation_reason` | Motif fourni lors de l'annulation du rendez-vous, le cas échéant. |
| `google_calendar_event_id` | ID de l'événement Google Calendar lié. Défini une fois la synchronisation du calendrier terminée ; `null` lorsqu'aucun calendrier n'est connecté ou pendant que la synchronisation est en cours. |
| `calendar_synced` | `true` une fois le rendez-vous lié à un événement de calendrier. |
| `imported` | `true` lorsque le rendez-vous a été importé depuis un calendrier externe plutôt que réservé directement. |
| `is_recurring` | `true` lorsque le rendez-vous fait partie d'une série récurrente. |
| `recurrence_frequency` | Fréquence de répétition du rendez-vous, en cas de récurrence. |
| `recurring_event_id` | ID de la série récurrente à laquelle ce rendez-vous appartient. |
| `recurring_interval` | Intervalle entre les répétitions, en cas de récurrence. |
| `recurring_sequence` | Position de ce rendez-vous au sein de sa série récurrente. |
| `end_after_x_occurrences` | Nombre d'occurrences après lesquelles la série récurrente se termine. |
| `booking_provider` | Système source d'où provient la réservation, lors d'une réservation via un fournisseur de réservation connecté. |

> **À propos de la synchronisation du calendrier :** Juste après avoir réservé ou modifié un rendez-vous, `google_calendar_event_id` peut encore être `null` et `calendar_synced` peut être `false` car la synchronisation s'exécute en arrière-plan un instant plus tard. Récupérez à nouveau le rendez-vous peu après pour voir les champs de calendrier renseignés.

---

## Trouver les créneaux disponibles

`GET /appointments/available-slots`

Renvoie les heures réellement libres pour un type d'événement donné entre deux moments. Il s'agit normalement du **premier** appel dans un flux de réservation : affichez ces créneaux, laissez la personne en choisir un, puis envoyez l'heure choisie à [Réserver un rendez-vous](#book-an-appointment).

La réponse prend déjà en compte les heures d'ouverture et la durée des créneaux du type d'événement, ses salles, les rendez-vous que vous avez déjà réservés, ainsi que tout ce qui est bloqué sur les agendas Google connectés — un créneau renvoyé ici est donc un créneau que vous pouvez réserver.

| Paramètre de requête | Requis | Description |
|---|---|---|
| `event_id` | Oui | Le type d'événement à vérifier. Doit appartenir à votre compte. |
| `start_time` | Oui | Début de la fenêtre pour laquelle vous souhaitez des créneaux, date-heure ISO 8601. |
| `end_time` | Oui | Fin de la fenêtre, date-heure ISO 8601. La journée de fin entière est incluse. |

Les résultats sont renvoyés regroupés par jour — et, lorsque le type d'événement utilise des salles, un groupe par salle et par jour :

| Champ | Description |
|---|---|
| `date` | Le jour couvert par le groupe, écrit `DD/MM/YYYY`. |
| `day` | Nom du jour de la semaine en minuscules, par exemple `monday`. |
| `room_name` | La salle ou la ressource à laquelle appartient ce groupe, lorsque le type d'événement utilise des salles. |
| `available_slots` | Les blocs réservables ce jour-là, du plus tôt au plus tard. |

Chaque entrée dans `available_slots` contient :

| Champ | Description |
|---|---|
| `start_time` | Début du bloc au format `HH:mm`. |
| `end_time` | Fin du bloc au format `HH:mm`. |
| `available` | `true` — seul le temps libre est renvoyé. |
| `spots_left` | Combien de réservations tiennent encore dans ce bloc. Uniquement présent sur les types d'événements acceptant plus d'une réservation par créneau. |

> **Les heures sont locales au type d'événement, et non en UTC.** `date`, `start_time` et `end_time` sont des valeurs d'horloge dans le fuseau horaire propre au type d'événement (son remplacement, ou le fuseau horaire de votre compte s'il n'en a pas). [Réserver un rendez-vous](#book-an-appointment) attend un instant UTC ISO 8601, convertissez donc le créneau que vous avez choisi avant de l'envoyer.

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

**Réponse** (`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 }
      ]
    }
  ]
}
```

Un jour sans aucune disponibilité n'apparaît tout simplement pas. L'absence de `event_id`, `start_time` ou `end_time` renvoie `400` ; un type d'événement qui n'est pas sur votre compte renvoie `404`.

---

## Réserver un rendez-vous

`POST /appointments`

Réserve un nouveau rendez-vous pour un contact sur l'un de vos types d'événements. L'heure de fin est calculée automatiquement à partir de la durée du créneau du type d'événement.

La réservation fait l'objet d'une vérification de conflit : si le créneau demandé chevauche un rendez-vous confirmé existant sur le même type d'événement, la requête échoue avec une erreur `409` et rien n'est créé.

| Champ | Requis | Description |
|---|---|---|
| `contact_id` | Oui | ID du contact pour lequel réserver. Doit appartenir à votre compte. |
| `event_id` | Oui | ID du type d'événement sur lequel réserver. Doit appartenir à votre compte. |
| `start_time` | Oui | Début souhaité sous forme de date-heure ISO 8601. |
| `room_name` | Non | Nom de la salle ou de la ressource, lorsque le type d'événement utilise des salles. |

**cURL** (utilisant le formulaire de requête `?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"])
```

**Réponse** (`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
  }
}
```

---

## Obtenir un rendez-vous

`GET /appointments/{appointmentId}`

Renvoie un rendez-vous unique par son ID, y compris son état de synchronisation avec le calendrier.

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

**Réponse** (`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
  }
}
```

---

## Lister les rendez-vous

`GET /appointments`

Liste les rendez-vous de votre compte, du plus récent au plus ancien, avec une pagination basée sur un curseur.

| Paramètre de requête | Requis | Description |
|---|---|---|
| `contact_id` | Non | Renvoie uniquement les rendez-vous pour ce contact. Les listes filtrées par contact incluent **uniquement les rendez-vous confirmés**. |
| `date` | Non | Renvoie uniquement les rendez-vous de ce jour calendaire (`YYYY-MM-DD`). **Nécessite `contact_id`.** |
| `status` | Non | Filtrer par `Confirmed` ou `Canceled`. Disponible uniquement **sans** `contact_id`. |
| `limit` | Non | Taille de la page, un entier entre 1 et 100. Par défaut `50`. |
| `cursor` | Non | La valeur `next_cursor` issue d'une réponse précédente. |

Quelques règles à garder à l'esprit :

- **Sans filtres**, vous obtenez tous les rendez-vous du compte, page par page.
- **Par contact** — définissez `contact_id` pour voir les rendez-vous confirmés d'un seul contact. Vous pouvez restreindre cela à une seule journée en transmettant également `date`.
- **Par statut** — définissez `status` (sans `contact_id`) pour lister uniquement les rendez-vous `Confirmed` ou uniquement `Canceled` sur l'ensemble du compte.
- Le filtre `date` sans `contact_id`, ou `status=Canceled` avec `contact_id`, renvoie une `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"])
```

**Réponse** (`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
}
```

Pour parcourir les résultats, transmettez le `next_cursor` d'une réponse en tant que `cursor` de la requête suivante. Continuez jusqu'à ce que `next_cursor` soit `null`. Consultez [Erreurs et pagination](errors-and-pagination.md) pour le modèle de pagination partagé.

---

## Mettre à jour un rendez-vous

`PUT /appointments/{appointmentId}`

Reprogrammez un rendez-vous ou modifiez ses détails. Envoyez uniquement les champs que vous souhaitez modifier — au moins un est requis. La combinaison du début et de la fin doit rester dans l'ordre chronologique (`end_time` doit être après `start_time`). Les modifications sont automatiquement synchronisées avec l'événement du calendrier lié.

| Champ | Description |
|---|---|
| `start_time` | Nouvelle date de début, au format ISO 8601. |
| `end_time` | Nouvelle date de fin, au format ISO 8601. Doit être postérieure à l'heure de début. |
| `room_name` | Nouveau nom de salle ou de ressource. |
| `description` | Nouvelle description, ou `null` pour l'effacer. |
| `summary` | Nouveau résumé, ou `null` pour l'effacer. |

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

**Réponse** (`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
  }
}
```

---

## Annuler un rendez-vous

`POST /appointments/{appointmentId}/cancel`

Annule un rendez-vous confirmé, en enregistrant éventuellement un motif. Le rendez-vous reste dans votre compte avec le statut `Canceled`, et l'événement de calendrier associé est supprimé automatiquement en arrière-plan. L'annulation d'un rendez-vous déjà annulé renvoie une erreur `400`.

| Champ | Requis | Description |
|---|---|---|
| `cancellation_reason` | Non | Motif de l'annulation, enregistré sur le rendez-vous. |

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

**Réponse** (`200 OK`) :

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

---

## Supprimer un rendez-vous

`DELETE /appointments/{appointmentId}`

Supprime définitivement un rendez-vous et ses références. Si vous souhaitez seulement annuler la réservation tout en conservant l'enregistrement, utilisez plutôt [annuler](#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"])
```

**Réponse** (`200 OK`) :

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

---

## Lister vos agendas Google connectés

`GET /appointments/google-calendars`

Renvoie les agendas Google disponibles sur ce compte, directement depuis Google — utile pour montrer au titulaire du compte un sélecteur de l'agenda à partir duquel importer ci-dessous, ou simplement pour confirmer que la connexion est active.

Cela ne fonctionne qu'une fois que le compte a connecté Google Calendar (Paramètres → Intégrations) avec au moins un accès en lecture. Si ce n'est pas le cas, ou si l'accès accordé n'inclut plus la portée de lecture du calendrier, vous recevrez une `400` vous demandant de le (re)connecter.

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

**Réponse** (`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"
    }
  ]
}
```

Chaque entrée correspond à la forme [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) de Google, donc les noms des champs suivent la `camelCase` de Google, et non la `snake_case` habituelle de cette API — il s'agit des données de Google transmises telles quelles, et non des nôtres. Une connexion manquante ou révoquée renvoie `400` avec une erreur expliquant que Google Calendar doit être (re)connecté.

---

## Importer des événements depuis un Google Calendar

`POST /appointments/import-calendar-events`

Récupère les événements déjà présents dans le ou les Google Calendar connectés d'une campagne ou d'un agent IA et les transforme en rendez-vous — utile la première fois que vous connectez un calendrier qui contient déjà des réservations. Cela peut prendre un certain temps (chaque événement passe par une extraction pour déterminer à qui il est destiné), donc cela ne s'exécute jamais en ligne : la requête met en file d'attente une tâche en arrière-plan et vous renvoie un `job_id` à interroger.

| Champ | Requis | Description |
|---|---|---|
| `campaign_id` | L'un de ces deux | La campagne dont le ou les calendriers connectés doivent être importés. |
| `agent_id` | L'un de ces deux | L'agent IA dont le ou les calendriers connectés doivent être importés. |
| `identifier` | Oui | `"EMAIL"` ou `"PHONE_NUMBER"` — quelle information de contact extraire de chaque événement de calendrier pour faire correspondre ou créer le contact auquel il appartient. |

Envoyez exactement l'un des deux `campaign_id` / `agent_id`, jamais les deux et jamais aucun des deux — toute autre combinaison renvoie une `400`. Celui que vous envoyez doit appartenir à votre compte, sinon vous recevrez une `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"])
```

**Réponse** (`202 Accepted`) :

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

`campaign_id` et `agent_id` renvoient celui que vous avez envoyé ; l'autre est toujours `null`.

### Interroger la tâche d'importation

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

**Réponse** (`200 OK`) :

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

| `status` | Signification |
|---|---|
| `queued` | Pas encore pris en charge. Continuez à interroger. |
| `processing` | L'importation est en cours. Continuez à interroger. |
| `completed` | Terminé — `message` contient un court résumé lisible par l'homme. |
| `failed` | Quelque chose a mal tourné — `error` contient la raison. |

`GET` sur un `jobId` qui n'existe pas (ou qui appartient à un compte différent) renvoie `404`.

---

## Intégrations de réservation de restaurant (Zenchef / Formitable)

Zenchef et Formitable sont des systèmes de réservation de restaurant via lesquels votre agent IA peut réserver de vraies tables. Chacun dispose d'un **widget de réservation public et non authentifié** (`https://api.youraiconnector.com/v1/zenchef-widget/...` et `https://api.youraiconnector.com/v1/formitable-widget/...`) qui s'affiche dans le chat pour le client — ces routes de widget sont des pages HTML simples destinées à être ouvertes dans un navigateur, et non des points de terminaison d'API JSON, elles ne sont donc pas documentées ici. Ce qui suit concerne les points de terminaison de gestion de compte : vérifier qu'un identifiant de restaurant appartient au titulaire du compte, puis l'ajouter, le mettre à jour ou le supprimer.

### Zenchef

La connexion d'un restaurant Zenchef est une vérification en deux étapes, afin que le titulaire du compte prouve qu'il gère réellement le restaurant avant qu'il ne soit relié au bot : vérifiez d'abord que l'identifiant existe (sans révéler le nom), puis demandez-lui de saisir lui-même le nom du restaurant et vérifiez qu'il correspond.

**Étape 1 — Vérifier l'existence d'un identifiant de restaurant**

`POST /appointments/zenchef-restaurants/check`

| Champ | Requis | Description |
|---|---|---|
| `restaurant_id` | Oui | L'identifiant du restaurant Zenchef à vérifier. |

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

**Réponse** (`200 OK`) :

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

`exists: false` signifie qu'aucun restaurant Zenchef ne possède cet identifiant — rien d'autre à faire. Limité à 10 vérifications par tranche de 5 minutes par compte ; tout dépassement renvoie `429`.

**Étape 2 — Vérifier le nom du restaurant**

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

| Champ | Requis | Description |
|---|---|---|
| `restaurant_id` | Oui | L'identifiant du restaurant Zenchef de l'étape 1. |
| `user_input_name` | Oui | Le nom saisi par le titulaire du compte — comparé au nom réel du restaurant sur Zenchef (insensible à la casse et aux espaces). |

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

**Réponse** (`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` signifie que le nom ne correspond pas — `restaurantDetails` est omis, demandez au titulaire du compte de réessayer. Limité à 3 tentatives par tranche de 5 minutes (plus strict que la vérification d'existence, car il s'agit de l'étape de preuve réelle). Un `restaurant_id` qui ne correspond plus sur Zenchef renvoie `404`.

**Étape 3 — Enregistrer le restaurant**

`POST /appointments/zenchef-restaurants`

| Champ | Requis | Description |
|---|---|---|
| `restaurant_id` | Oui | 1 à 64 caractères, lettres/chiffres/underscore/tiret. |
| `restaurant_name` | Oui | Le nom du restaurant vérifié à l'étape 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" }'
```

**Réponse** (`201 Created`) :

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

**Mettre à jour un restaurant Zenchef enregistré**

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

| Champ | Requis | Description |
|---|---|---|
| `restaurant_name` | Non | Nouveau nom d'affichage. |
| `is_active` | Non | Définissez `false` pour empêcher le bot de réserver dans ce restaurant sans le supprimer. |

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

**Réponse** (`200 OK`) : même structure que la réponse d'enregistrement ci-dessus.

**Supprimer un restaurant 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"
```

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

Un `restaurantId` qui n'est pas actuellement sur le compte renvoie `404` lors d'une mise à jour ou d'une suppression.

### Formitable

Formitable n'a pas besoin de la vérification de nom en deux étapes comme Zenchef — ses identifiants de restaurant sont déjà limités par entreprise, donc un seul appel de vérification suffit. Il dispose également d'une recherche de détails utilisée pour mettre en cache l'URL du site web du restaurant lors de la configuration.

**Vérifier un identifiant de restaurant**

`POST /appointments/formitable-restaurants/verify`

| Champ | Requis | Description |
|---|---|---|
| `restaurant_id` | Oui | L'identifiant de restaurant Formitable. |
| `language` | Non | Balise de langue pour la requête de test. La valeur par défaut est `"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" }'
```

**Réponse** (`200 OK`) :

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

Un `restaurant_id` que Formitable ne reconnaît pas renvoie `404`. Limité à 10 tentatives par tranche de 5 minutes par compte.

**Obtenir les détails du restaurant**

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

Récupère le profil public du restaurant depuis Formitable, y compris son site web — utilisé pour mettre en cache l'URL du site web lors de la configuration du restaurant. `language` est un paramètre de requête optionnel, dont la valeur par défaut est `"en"`.

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

**Réponse** (`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"
  }
}
```

**Enregistrer le restaurant**

`POST /appointments/formitable-restaurants`

| Champ | Requis | Description |
|---|---|---|
| `restaurant_id` | Oui | 1–64 caractères, lettres/chiffres/underscore/tiret. |
| `restaurant_name` | Oui | Nom d'affichage. |
| `language` | Oui | Étiquette de langue ISO, par ex. `"en"` ou `"en-GB"`. |
| `website_url` | Non | Le site web du restaurant, issu de la recherche de détails ci-dessus. Doit être `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"
  }'
```

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

**Mettre à jour un restaurant Formitable enregistré**

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

| Champ | Requis | Description |
|---|---|---|
| `restaurant_name` | Non | Nouveau nom d'affichage. |
| `language` | Non | Nouvelle étiquette de langue ISO. |
| `is_active` | Non | Définissez `false` pour empêcher le bot de réserver auprès de ce restaurant sans le supprimer. |
| `website_url` | Non | Nouvelle URL de site web. |

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

**Réponse** (`200 OK`) : même structure que la réponse d'enregistrement ci-dessus.

**Supprimer un restaurant 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"
```

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

Un `restaurantId` qui n'est pas actuellement sur le compte renvoie `404` lors d'une mise à jour ou d'une suppression.

> **Structure d'erreur sur tous les points de terminaison Zenchef/Formitable :** contrairement au reste de cette page, les erreurs ici portent leur statut deux fois — une fois en tant que statut HTTP et une fois en tant que `error_code` dans le corps — par exemple `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Gérez-la de la même manière que toute autre erreur : vérifiez `success`, lisez `error` pour le message.

---

## Erreurs de l'API Rendez-vous

Les points de terminaison des rendez-vous renvoient l'enveloppe d'erreur standard :

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

| Statut | Quand cela se produit sur un point de terminaison de rendez-vous |
|---|---|
| `400` | Un champ requis est manquant ou invalide — par exemple un `start_time` incorrect, une `end_time` qui n'est pas postérieure à `start_time`, une combinaison de filtres invalide, aucun champ à mettre à jour, ou un rendez-vous déjà annulé. |
| `404` | Le rendez-vous, le contact ou le type d'événement est introuvable. |
| `409` | Le créneau horaire demandé est déjà pris (conflit de réservation). |

Les codes partagés que chaque point de terminaison peut renvoyer — `401`, `403` (votre forfait n'inclut pas l'accès à l'API), `429` (limite de débit) et `500` — sont répertoriés avec des conseils de nouvelle tentative dans [Erreurs et pagination](errors-and-pagination.md).

---

## Étapes suivantes

- [Contacts](contacts.md) — créez et recherchez les contacts pour lesquels vous effectuez des réservations.
- [Messages et conversations](messages.md) — envoyez une confirmation ou un rappel à un contact.
- [Webhooks](webhooks.md) — soyez averti lorsque des rendez-vous sont modifiés.
