
# Appuntamenti

L'API Appuntamenti ti consente di prenotare appuntamenti per i tuoi contatti sui tuoi tipi di evento, per poi recuperarli, elencarli, aggiornarli, annullarli o eliminarli. Risponde anche alla domanda che sorge per prima nella maggior parte dei flussi di prenotazione — quali orari sono effettivamente liberi — e copre il lato calendario: elencando i Google Calendar che hai collegato e importando gli eventi che vi sono già presenti. Quando una connessione a Google Calendar è attiva, l'evento del calendario corrispondente viene creato e mantenuto sincronizzato automaticamente in background. I ristoranti che utilizzano Zenchef o Formitable per il proprio sistema di prenotazione possono essere verificati e collegati qui, in modo che l'Agente IA prenoti tavoli reali invece di appuntamenti interni.

Tutti i percorsi in questa pagina sono relativi all'URL di base `https://api.youraiconnector.com/v1`. Ogni richiesta richiede la tua chiave API: consulta [Autenticazione](authentication.md) per l'elenco completo delle modalità di invio. Gli esempi seguenti utilizzano l'intestazione `X-API-Key`, con un esempio cURL che mostra anche il formato di query `?apiKey=`.

> **Eventi vs. appuntamenti:** Una *tipologia di evento* è la definizione di uno slot prenotabile (il tipo di riunione, la sua durata, le sue sale). Un *appuntamento* è una singola istanza prenotata di una tipologia di evento per uno specifico contatto. Prenoti un appuntamento facendo riferimento al contatto e alla tipologia di evento.

---

## L'oggetto appuntamento

Ogni endpoint che restituisce un appuntamento utilizza la stessa struttura:

| Campo | Descrizione |
|---|---|
| `id` | ID univoco dell'appuntamento. |
| `contact_id` | ID del contatto con cui è prenotato l'appuntamento. |
| `event_id` | ID della tipologia di evento su cui è stato prenotato l'appuntamento. |
| `status` | `Confirmed` o `Canceled`. |
| `start_time` | Inizio dell'appuntamento, ISO 8601 in UTC. |
| `end_time` | Fine dell'appuntamento, ISO 8601 in UTC. |
| `created_at` | Quando è stato creato l'appuntamento. |
| `last_modified_at` | Quando l'appuntamento è stato modificato l'ultima volta. |
| `room_name` | Sala o risorsa in cui è prenotato l'appuntamento, quando la tipologia di evento utilizza le sale. |
| `description` | Descrizione a formato libero dell'appuntamento. |
| `summary` | Breve riepilogo o titolo. |
| `cancelation_reason` | Motivo fornito al momento dell'annullamento dell'appuntamento, se presente. |
| `google_calendar_event_id` | ID dell'evento di Google Calendar collegato. Impostato una volta completata la sincronizzazione del calendario; `null` quando nessun calendario è collegato o mentre la sincronizzazione è ancora in corso. |
| `calendar_synced` | `true` una volta che l'appuntamento è collegato a un evento di calendario. |
| `imported` | `true` quando l'appuntamento è stato importato da un calendario esterno anziché prenotato direttamente. |
| `is_recurring` | `true` quando l'appuntamento fa parte di una serie ricorrente. |
| `recurrence_frequency` | Frequenza di ripetizione dell'appuntamento, quando ricorrente. |
| `recurring_event_id` | ID della serie ricorrente a cui appartiene questo appuntamento. |
| `recurring_interval` | Intervallo tra le ripetizioni, quando ricorrente. |
| `recurring_sequence` | Posizione di questo appuntamento all'interno della sua serie ricorrente. |
| `end_after_x_occurrences` | Numero di occorrenze dopo le quali termina la serie ricorrente. |
| `booking_provider` | Sistema di origine da cui proviene la prenotazione, quando prenotato tramite un fornitore di prenotazioni collegato. |

> **Informazioni sulla sincronizzazione del calendario:** Subito dopo aver prenotato o modificato un appuntamento, `google_calendar_event_id` potrebbe essere ancora `null` e `calendar_synced` potrebbe essere `false` perché la sincronizzazione viene eseguita in background poco dopo. Recupera nuovamente l'appuntamento poco dopo per visualizzare i campi del calendario popolati.

---

## Trova gli slot disponibili

`GET /appointments/available-slots`

Restituisce gli orari che sono effettivamente liberi per un tipo di evento tra due momenti. Questa è solitamente la **prima** chiamata in un flusso di prenotazione: mostra questi slot, lascia che la persona ne scelga uno, quindi invia l'orario scelto a [Prenota un appuntamento](#book-an-appointment).

La risposta tiene già conto degli orari di apertura e della durata dello slot del tipo di evento, delle sue sale, degli appuntamenti che hai già prenotato su di esso e di tutto ciò che è bloccato sui Google Calendar collegati — quindi uno slot restituito qui è uno slot che puoi prenotare.

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `event_id` | Sì | Il tipo di evento da controllare. Deve appartenere al tuo account. |
| `start_time` | Sì | Inizio della finestra per cui desideri gli slot, data-ora ISO 8601. |
| `end_time` | Sì | Fine della finestra, data-ora ISO 8601. L'intero giorno finale è incluso. |

I risultati vengono restituiti raggruppati per giorno — e, quando il tipo di evento utilizza le sale, un gruppo per sala per giorno:

| Campo | Descrizione |
|---|---|
| `date` | Il giorno coperto dal gruppo, scritto `DD/MM/YYYY`. |
| `day` | Nome del giorno della settimana in minuscolo, ad esempio `monday`. |
| `room_name` | La sala o la risorsa a cui appartiene questo gruppo, quando il tipo di evento utilizza le sale. |
| `available_slots` | I blocchi prenotabili in quel giorno, dal più presto al più tardi. |

Ogni voce in `available_slots` ha:

| Campo | Descrizione |
|---|---|
| `start_time` | Inizio del blocco come `HH:mm`. |
| `end_time` | Fine del blocco come `HH:mm`. |
| `available` | `true` — viene restituito solo il tempo libero. |
| `spots_left` | Quante prenotazioni rientrano ancora in questo blocco. Presente solo sui tipi di evento che accettano più di una prenotazione per slot. |

> **Gli orari sono locali rispetto al tipo di evento, non UTC.** `date`, `start_time` e `end_time` sono valori di orologio locale nel fuso orario del tipo di evento (il suo override, o il fuso orario del tuo account quando non ne ha uno). [Prenota un appuntamento](#book-an-appointment) si aspetta un istante UTC ISO 8601, quindi converti lo slot che hai scelto prima di inviarlo.

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

**Risposta** (`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 giorno senza disponibilità semplicemente non appare. `event_id`, `start_time` o `end_time` mancanti restituiscono `400`; un tipo di evento che non è sul tuo account restituisce `404`.

---

## Prenota un appuntamento

`POST /appointments`

Prenota un nuovo appuntamento per un contatto su una delle tue tipologie di evento. L'orario di fine viene calcolato automaticamente in base alla durata dello slot della tipologia di evento.

La prenotazione viene controllata per verificare la presenza di conflitti: se lo slot richiesto si sovrappone a un appuntamento confermato esistente sulla stessa tipologia di evento, la richiesta fallisce con un `409` e non viene creato nulla.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `contact_id` | Sì | ID del contatto per cui prenotare. Deve appartenere al tuo account. |
| `event_id` | Sì | ID della tipologia di evento su cui prenotare. Deve appartenere al tuo account. |
| `start_time` | Sì | Inizio desiderato come data-ora ISO 8601. |
| `room_name` | No | Nome della sala o della risorsa, quando la tipologia di evento utilizza le sale. |

**cURL** (utilizzando il formato di query `?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"])
```

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

---

## Ottieni un appuntamento

`GET /appointments/{appointmentId}`

Restituisce un singolo appuntamento tramite il suo ID, incluso il suo stato di sincronizzazione del calendario.

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

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

---

## Elenca appuntamenti

`GET /appointments`

Elenca gli appuntamenti per il tuo account, dal più recente, con paginazione basata su cursore.

| Parametro di query | Obbligatorio | Descrizione |
|---|---|---|
| `contact_id` | No | Restituisce solo gli appuntamenti per questo contatto. Gli elenchi filtrati per contatto includono **solo gli appuntamenti confermati**. |
| `date` | No | Restituisce solo gli appuntamenti in questo giorno del calendario (`YYYY-MM-DD`). **Richiede `contact_id`.** |
| `status` | No | Filtra per `Confirmed` o `Canceled`. Disponibile solo **senza** `contact_id`. |
| `limit` | No | Dimensione della pagina, un numero intero tra 1 e 100. Predefinito `50`. |
| `cursor` | No | Il valore `next_cursor` da una risposta precedente. |

Alcune regole da tenere a mente:

- **Senza filtri**, ottieni ogni appuntamento sull'account, pagina per pagina.
- **Per contatto** — imposta `contact_id` per vedere gli appuntamenti confermati di un contatto. Puoi restringere il campo a un singolo giorno passando anche `date`.
- **Per stato** — imposta `status` (senza `contact_id`) per elencare solo gli appuntamenti `Confirmed` o solo `Canceled` nell'account.
- Il filtro `date` senza `contact_id`, o `status=Canceled` insieme a `contact_id`, restituisce un `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"])
```

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

Per scorrere i risultati, passa il `next_cursor` da una risposta come `cursor` della richiesta successiva. Continua finché `next_cursor` non è `null`. Vedi [Errori e Paginazione](errors-and-pagination.md) per il pattern di paginazione condiviso.

---

## Aggiorna un appuntamento

`PUT /appointments/{appointmentId}`

Ripianifica un appuntamento o modifica i suoi dettagli. Invia solo i campi che desideri modificare: almeno uno è obbligatorio. L'inizio e la fine combinati devono rimanere in ordine cronologico (`end_time` deve essere successivo a `start_time`). Le modifiche vengono sincronizzate automaticamente con l'evento del calendario collegato.

| Campo | Descrizione |
|---|---|
| `start_time` | Nuovo inizio, data-ora in formato ISO 8601. |
| `end_time` | Nuova fine, data-ora in formato ISO 8601. Deve essere successiva all'orario di inizio. |
| `room_name` | Nuovo nome della stanza o della risorsa. |
| `description` | Nuova descrizione, o `null` per cancellarla. |
| `summary` | Nuovo riepilogo, o `null` per cancellarlo. |

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

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

---

## Annulla un appuntamento

`POST /appointments/{appointmentId}/cancel`

Annulla un appuntamento confermato, registrando facoltativamente un motivo. L'appuntamento rimane nel tuo account con lo stato `Canceled` e l'evento del calendario collegato viene rimosso automaticamente in background. L'annullamento di un appuntamento già annullato restituisce un `400`.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `cancellation_reason` | No | Motivo dell'annullamento, memorizzato nell'appuntamento. |

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

**Risposta** (`200 OK`):

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

---

## Elimina un appuntamento

`DELETE /appointments/{appointmentId}`

Elimina definitivamente un appuntamento e i suoi riferimenti. Se desideri solo annullare la prenotazione mantenendo il record, utilizza invece [annulla](#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"])
```

**Risposta** (`200 OK`):

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

---

## Elenca i tuoi Google Calendar collegati

`GET /appointments/google-calendars`

Restituisce i Google Calendar disponibili su questo account, direttamente da Google — utile per mostrare al titolare dell'account un selettore da cui importare il calendario qui sotto, o semplicemente per confermare che la connessione è attiva.

Questo funziona solo una volta che l'account ha collegato Google Calendar (Impostazioni → Integrazioni) con almeno l'accesso in lettura. Se non lo ha fatto, o se l'accesso concesso non include più l'ambito di lettura del calendario, riceverai un `400` che ti invita a (ri)collegarlo.

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

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

Ogni voce ha la forma [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) di Google, quindi i nomi dei campi seguono lo `camelCase` di Google, non il solito `snake_case` di questa API: si tratta dei dati di Google trasmessi così come sono, non dei nostri. Una connessione mancante o revocata restituisce `400` con un errore che spiega che Google Calendar deve essere (ri)collegato.

---

## Importa eventi da un Google Calendar

`POST /appointments/import-calendar-events`

Estrae gli eventi già presenti nel/i Google Calendar collegato/i di una campagna o di un Agente AI e li trasforma in appuntamenti; è utile la prima volta che si collega un calendario che ha già delle prenotazioni. Questa operazione può richiedere del tempo (ogni evento viene analizzato per capire a chi è destinato), quindi non viene mai eseguita in linea: la richiesta accoda un processo in background e ti restituisce un `job_id` da interrogare.

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `campaign_id` | Uno di questi due | La campagna da cui importare i calendari collegati. |
| `agent_id` | Uno di questi due | L'Agente AI da cui importare i calendari collegati. |
| `identifier` | Sì | `"EMAIL"` o `"PHONE_NUMBER"`: quale informazione di contatto estrarre da ogni evento del calendario per trovare o creare il contatto a cui appartiene. |

Invia esattamente uno tra `campaign_id` / `agent_id`, mai entrambi e mai nessuno dei due: qualsiasi altra combinazione restituisce un `400`. Qualunque tu scelga di inviare deve appartenere al tuo account, altrimenti riceverai un `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"])
```

**Risposta** (`202 Accepted`):

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

`campaign_id` e `agent_id` rimandano quello che hai inviato; l'altro è sempre `null`.

### Interroga il processo di importazione

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

**Risposta** (`200 OK`):

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

| `status` | Significato |
|---|---|
| `queued` | Non ancora preso in carico. Continua a interrogare. |
| `processing` | L'importazione è in corso. Continua a interrogare. |
| `completed` | Completato: `message` contiene un breve riepilogo leggibile. |
| `failed` | Qualcosa è andato storto: `error` contiene il motivo. |

`GET` su un `jobId` che non esiste (o appartiene a un account diverso) restituisce `404`.

---

## Integrazioni per prenotazioni di ristoranti (Zenchef / Formitable)

Zenchef e Formitable sono sistemi di prenotazione per ristoranti tramite i quali il tuo Agente AI può prenotare tavoli reali. Ognuno dispone di un **widget di prenotazione pubblico e non autenticato** (`https://api.youraiconnector.com/v1/zenchef-widget/...` e `https://api.youraiconnector.com/v1/formitable-widget/...`) che viene visualizzato all'interno della chat per il cliente; tali percorsi del widget sono semplici pagine HTML destinate ad essere aperte in un browser, non endpoint API JSON, quindi non sono documentati qui. Di seguito sono riportati gli endpoint di gestione dell'account: verifica che un ID ristorante appartenga al titolare dell'account, quindi aggiunta, aggiornamento o rimozione dello stesso.

### Zenchef

La connessione di un ristorante Zenchef è una verifica in due passaggi, in modo che il titolare dell'account dimostri di gestire effettivamente il ristorante prima che venga collegato al bot: prima si verifica che l'ID esista (senza rivelare il nome), poi si chiede di digitare il nome del ristorante e si verifica che corrisponda.

**Passaggio 1 — Verificare che un ID ristorante esista**

`POST /appointments/zenchef-restaurants/check`

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_id` | Sì | L'ID del ristorante Zenchef da verificare. |

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

**Risposta** (`200 OK`):

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

`exists: false` significa che nessun ristorante Zenchef possiede quell'ID: non c'è altro da fare. Limitato a 10 controlli ogni 5 minuti per account; il superamento di questo limite restituisce `429`.

**Passaggio 2 — Verificare il nome del ristorante**

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

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_id` | Sì | L'ID del ristorante Zenchef dal passaggio 1. |
| `user_input_name` | Sì | Il nome digitato dal titolare dell'account: confrontato con il nome reale del ristorante su Zenchef (non sensibile a maiuscole/minuscole o spazi). |

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

**Risposta** (`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 che il nome non corrisponde: `restaurantDetails` viene omesso, chiedi al titolare dell'account di riprovare. Limitato a 3 tentativi ogni 5 minuti (più restrittivo del controllo di esistenza, poiché questo è il passaggio di prova effettivo). Un `restaurant_id` che non è più risolvibile su Zenchef restituisce `404`.

**Passaggio 3 — Salvare il ristorante**

`POST /appointments/zenchef-restaurants`

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_id` | Sì | 1–64 caratteri, lettere/numeri/underscore/trattino. |
| `restaurant_name` | Sì | Il nome del ristorante verificato dal passaggio 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" }'
```

**Risposta** (`201 Created`):

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

**Aggiornare un ristorante Zenchef salvato**

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

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_name` | No | Nuovo nome visualizzato. |
| `is_active` | No | Imposta `false` per impedire al bot di effettuare prenotazioni presso questo ristorante senza rimuoverlo. |

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

**Risposta** (`200 OK`): stessa struttura della risposta di salvataggio sopra.

**Rimuovere un ristorante 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"
```

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

Un `restaurantId` non attualmente presente nell'account restituisce `404` in caso di aggiornamento o eliminazione.

### Formitable

Formitable non richiede la verifica del nome in due passaggi necessaria per Zenchef: i suoi ID ristorante sono già limitati per attività, quindi una sola chiamata di verifica è sufficiente. Dispone inoltre di una ricerca dei dettagli utilizzata per memorizzare nella cache l'URL del sito web del ristorante durante la configurazione.

**Verificare un ID ristorante**

`POST /appointments/formitable-restaurants/verify`

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_id` | Sì | L'ID ristorante Formitable. |
| `language` | No | Tag della lingua per la richiesta di sondaggio. L'impostazione predefinita è `"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" }'
```

**Risposta** (`200 OK`):

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

Un `restaurant_id` non riconosciuto da Formitable restituisce `404`. Limitato a 10 tentativi ogni 5 minuti per account.

**Ottenere i dettagli del ristorante**

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

Recupera il profilo pubblico del ristorante da Formitable, incluso il suo sito web: utilizzato per memorizzare nella cache l'URL del sito web durante la configurazione del ristorante. `language` è un parametro di query opzionale, con valore predefinito `"en"`.

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

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

**Salva il ristorante**

`POST /appointments/formitable-restaurants`

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_id` | Sì | 1–64 caratteri, lettere/numeri/underscore/trattino. |
| `restaurant_name` | Sì | Nome visualizzato. |
| `language` | Sì | Tag lingua ISO, ad es. `"en"` o `"en-GB"`. |
| `website_url` | No | Il sito web del ristorante, dalla ricerca dei dettagli sopra. Deve essere `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"
  }'
```

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

**Aggiorna un ristorante Formitable salvato**

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

| Campo | Obbligatorio | Descrizione |
|---|---|---|
| `restaurant_name` | No | Nuovo nome visualizzato. |
| `language` | No | Nuovo tag lingua ISO. |
| `is_active` | No | Imposta `false` per impedire al bot di effettuare prenotazioni presso questo ristorante senza rimuoverlo. |
| `website_url` | No | Nuovo URL del sito 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 }'
```

**Risposta** (`200 OK`): stessa struttura della risposta di salvataggio sopra.

**Rimuovi un ristorante 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"
```

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

Un `restaurantId` non attualmente presente nell'account restituisce `404` in caso di aggiornamento o eliminazione.

> **Struttura dell'errore su tutti gli endpoint Zenchef/Formitable:** a differenza del resto di questa pagina, gli errori qui riportano il loro stato due volte — una come stato HTTP e una come `error_code` nel corpo — ad esempio `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Gestiscilo allo stesso modo di qualsiasi altro errore: controlla `success`, leggi `error` per il messaggio.

---

## Errori dell'API Appuntamenti

Gli endpoint degli appuntamenti restituiscono il formato di errore standard:

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

| Stato | Quando si verifica su un endpoint di appuntamento |
|---|---|
| `400` | Un campo obbligatorio manca o non è valido — ad esempio un `start_time` errato, un `end_time` non successivo a `start_time`, una combinazione di filtri non valida, nessun campo da aggiornare o un appuntamento già annullato. |
| `404` | L'appuntamento, il contatto o il tipo di evento non è stato trovato. |
| `409` | La fascia oraria richiesta è già occupata (conflitto di prenotazione). |

I codici condivisi che ogni endpoint può restituire — `401`, `403` (il tuo piano non include l'accesso all'API), `429` (limite di frequenza) e `500` — sono elencati con indicazioni sui tentativi in [Errori e Paginazione](errors-and-pagination.md).

---

## Passaggi successivi

- [Contatti](contacts.md) — crea e cerca i contatti per cui effettui le prenotazioni.
- [Messaggi e conversazioni](messages.md) — invia a un contatto una conferma o un promemoria.
- [Webhook](webhooks.md) — ricevi notifiche quando gli appuntamenti cambiano.
