
# Citas

La API de Citas le permite reservar citas para sus contactos en sus tipos de evento, así como obtener, listar, actualizar, cancelar o eliminar dichas citas. También responde a la pregunta que surge primero en la mayoría de los flujos de reserva —qué horarios están realmente libres— y cubre el aspecto del calendario: listar los calendarios de Google que tiene conectados e importar eventos que ya existen en ellos. Cuando una conexión de Google Calendar está activa, el evento de calendario correspondiente se crea y se mantiene sincronizado automáticamente en segundo plano. Los restaurantes que utilizan Zenchef o Formitable para su propio sistema de reservas también pueden ser verificados y conectados aquí, de modo que el Agente de IA reserve mesas reales en lugar de citas internas.

Todas las rutas en esta página son relativas a la URL base `https://api.youraiconnector.com/v1`. Cada solicitud requiere su clave de API; consulte [Autenticación](authentication.md) para ver la lista completa de formas de enviarla. Los ejemplos a continuación utilizan el encabezado `X-API-Key`, y un ejemplo de cURL muestra también el formato de consulta `?apiKey=`.

> **Eventos vs. citas:** Un *tipo de evento* es una definición de espacio reservable (el tipo de reunión, su duración, sus salas). Una *cita* es una instancia reservada de un tipo de evento para un contacto específico. Usted reserva una cita haciendo referencia al contacto y al tipo de evento.

---

## El objeto de cita

Cada endpoint que devuelve una cita utiliza la misma estructura:

| Campo | Descripción |
|---|---|
| `id` | ID único de la cita. |
| `contact_id` | ID del contacto con el que se reserva la cita. |
| `event_id` | ID del tipo de evento en el que se reservó la cita. |
| `status` | `Confirmed` o `Canceled`. |
| `start_time` | Inicio de la cita, ISO 8601 en UTC. |
| `end_time` | Fin de la cita, ISO 8601 en UTC. |
| `created_at` | Cuándo se creó la cita. |
| `last_modified_at` | Cuándo se cambió la cita por última vez. |
| `room_name` | Sala o recurso en el que se reserva la cita, cuando el tipo de evento utiliza salas. |
| `description` | Descripción de formato libre de la cita. |
| `summary` | Resumen o título breve. |
| `cancelation_reason` | Motivo proporcionado cuando se canceló la cita, si existe. |
| `google_calendar_event_id` | ID del evento de Google Calendar vinculado. Se establece una vez que se completa la sincronización del calendario; `null` cuando no hay ningún calendario conectado o mientras la sincronización aún está en curso. |
| `calendar_synced` | `true` una vez que la cita está vinculada a un evento de calendario. |
| `imported` | `true` cuando la cita se importó desde un calendario externo en lugar de reservarse directamente. |
| `is_recurring` | `true` cuando la cita es parte de una serie recurrente. |
| `recurrence_frequency` | Con qué frecuencia se repite la cita, cuando es recurrente. |
| `recurring_event_id` | ID de la serie recurrente a la que pertenece esta cita. |
| `recurring_interval` | Intervalo entre repeticiones, cuando es recurrente. |
| `recurring_sequence` | Posición de esta cita dentro de su serie recurrente. |
| `end_after_x_occurrences` | Número de ocurrencias después de las cuales finaliza la serie recurrente. |
| `booking_provider` | Sistema de origen del que proviene la reserva, cuando se reserva a través de un proveedor de reservas conectado. |

> **Acerca de la sincronización de calendario:** Justo después de reservar o cambiar una cita, `google_calendar_event_id` puede seguir siendo `null` y `calendar_synced` puede ser `false` porque la sincronización se ejecuta en segundo plano un momento después. Vuelva a obtener la cita poco después para ver los campos de calendario completados.

---

## Encontrar espacios disponibles

`GET /appointments/available-slots`

Devuelve los horarios que están realmente libres en un tipo de evento entre dos momentos. Esta suele ser la **primera** llamada en un flujo de reserva: mostrar estos espacios, dejar que la persona elija uno y, a continuación, enviar la hora elegida a [Reservar una cita](#book-an-appointment).

La respuesta ya tiene en cuenta el horario de apertura y la duración del espacio del tipo de evento, sus salas, las citas que ya ha reservado en él y todo lo bloqueado en los calendarios de Google conectados; por lo tanto, un espacio que se devuelve aquí es uno que puede reservar.

| Parámetro de consulta | Requerido | Descripción |
|---|---|---|
| `event_id` | Sí | El tipo de evento a consultar. Debe pertenecer a su cuenta. |
| `start_time` | Sí | Inicio de la ventana para la que desea espacios, fecha y hora en formato ISO 8601. |
| `end_time` | Sí | Fin de la ventana, fecha y hora en formato ISO 8601. Se incluye el día final completo. |

Los resultados se devuelven agrupados por día y, cuando el tipo de evento utiliza salas, un grupo por sala por día:

| Campo | Descripción |
|---|---|
| `date` | El día que cubre el grupo, escrito `DD/MM/YYYY`. |
| `day` | Nombre del día de la semana en minúsculas, por ejemplo `monday`. |
| `room_name` | La sala o recurso al que pertenece este grupo, cuando el tipo de evento utiliza salas. |
| `available_slots` | Los bloques reservables en ese día, ordenados del más temprano al más tardío. |

Cada entrada en `available_slots` tiene:

| Campo | Descripción |
|---|---|
| `start_time` | Inicio del bloque como `HH:mm`. |
| `end_time` | Fin del bloque como `HH:mm`. |
| `available` | `true` — solo se devuelve el tiempo libre. |
| `spots_left` | Cuántas reservas aún caben en este bloque. Solo presente en tipos de evento que aceptan más de una reserva por espacio. |

> **Los horarios son locales al tipo de evento, no UTC.** `date`, `start_time` y `end_time` son valores de reloj de pared en la zona horaria propia del tipo de evento (su anulación, o la zona horaria de su cuenta cuando no tiene ninguna). [Reservar una cita](#book-an-appointment) espera un instante UTC en formato ISO 8601, así que convierta el espacio que eligió antes de enviarlo.

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

**Respuesta** (`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 día sin disponibilidad simplemente no aparece. Si faltan `event_id`, `start_time` o `end_time`, se devuelve `400`; un tipo de evento que no está en su cuenta devuelve `404`.

---

## Reservar una cita

`POST /appointments`

Reserva una nueva cita para un contacto en uno de sus tipos de evento. La hora de finalización se calcula automáticamente a partir de la duración del espacio del tipo de evento.

La reserva se verifica para detectar conflictos: si el espacio solicitado se superpone con una cita confirmada existente en el mismo tipo de evento, la solicitud falla con un `409` y no se crea nada.

| Campo | Requerido | Descripción |
|---|---|---|
| `contact_id` | Sí | ID del contacto para el que reservar. Debe pertenecer a su cuenta. |
| `event_id` | Sí | ID del tipo de evento en el que reservar. Debe pertenecer a su cuenta. |
| `start_time` | Sí | Inicio deseado como fecha y hora ISO 8601. |
| `room_name` | No | Nombre de la sala o recurso, cuando el tipo de evento utiliza salas. |

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

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

**JavaScript**

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

**Python**

```python
import requests

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

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

---

## Obtener una cita

`GET /appointments/{appointmentId}`

Devuelve una única cita por su ID, incluido su estado de sincronización de 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"])
```

**Respuesta** (`200 OK`):

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

---

## Listar citas

`GET /appointments`

Enumera las citas de su cuenta, de la más reciente a la más antigua, con paginación basada en cursor.

| Parámetro de consulta | Obligatorio | Descripción |
|---|---|---|
| `contact_id` | No | Solo devuelve citas para este contacto. Los listados filtrados por contacto incluyen **solo citas confirmadas**. |
| `date` | No | Solo devuelve citas en este día del calendario (`YYYY-MM-DD`). **Requiere `contact_id`.** |
| `status` | No | Filtrar por `Confirmed` o `Canceled`. Solo disponible **sin** `contact_id`. |
| `limit` | No | Tamaño de página, un número entero entre 1 y 100. El valor predeterminado es `50`. |
| `cursor` | No | El valor `next_cursor` de una respuesta anterior. |

Algunas reglas a tener en cuenta:

- **Sin filtros**, obtendrá todas las citas de la cuenta, página por página.
- **Por contacto**: establezca `contact_id` para ver las citas confirmadas de un contacto. Puede restringir esto a un solo día pasando también `date`.
- **Por estado**: establezca `status` (sin `contact_id`) para listar solo las citas `Confirmed` o solo las `Canceled` en toda la cuenta.
- El filtro `date` sin `contact_id`, o `status=Canceled` junto con `contact_id`, devuelve 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"])
```

**Respuesta** (`200 OK`):

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

Para paginar los resultados, pase el `next_cursor` de una respuesta como el `cursor` de la siguiente solicitud. Continúe hasta que `next_cursor` sea `null`. Consulte [Errores y paginación](errors-and-pagination.md) para conocer el patrón de paginación compartido.

---

## Actualizar una cita

`PUT /appointments/{appointmentId}`

Reprogramar una cita o cambiar sus detalles. Envíe solo los campos que desea cambiar; al menos uno es obligatorio. El inicio y el fin combinados deben permanecer en orden cronológico (`end_time` debe ser posterior a `start_time`). Los cambios se sincronizan automáticamente con el evento del calendario vinculado.

| Campo | Descripción |
|---|---|
| `start_time` | Nueva fecha y hora de inicio, en formato ISO 8601. |
| `end_time` | Nueva fecha y hora de finalización, en formato ISO 8601. Debe ser posterior a la hora de inicio. |
| `room_name` | Nuevo nombre de sala o recurso. |
| `description` | Nueva descripción, o `null` para borrarla. |
| `summary` | Nuevo resumen, o `null` para borrarlo. |

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

**Respuesta** (`200 OK`):

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

---

## Cancelar una cita

`POST /appointments/{appointmentId}/cancel`

Cancela una cita confirmada, registrando opcionalmente un motivo. La cita permanece en su cuenta con el estado `Canceled` y el evento de calendario vinculado se elimina automáticamente en segundo plano. Cancelar una cita que ya ha sido cancelada devuelve un `400`.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `cancellation_reason` | No | Motivo de la cancelación, almacenado en la cita. |

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

**Respuesta** (`200 OK`):

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

---

## Eliminar una cita

`DELETE /appointments/{appointmentId}`

Elimina permanentemente una cita y sus referencias. Si solo desea cancelar la reserva manteniendo el registro, utilice [cancelar](#cancel-an-appointment) en su lugar.

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

**Respuesta** (`200 OK`):

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

---

## Listar sus calendarios de Google conectados

`GET /appointments/google-calendars`

Devuelve los calendarios de Google disponibles en esta cuenta, directamente desde Google; es útil para mostrar al titular de la cuenta un selector desde el cual importar, o simplemente para confirmar que la conexión está activa.

Esto solo funciona una vez que la cuenta ha conectado Google Calendar (Ajustes → Integraciones) con al menos acceso de lectura. Si no lo ha hecho, o si el acceso concedido ya no incluye el ámbito de lectura de calendario, obtendrá un `400` que le indicará que lo (re)conecte.

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

**Respuesta** (`200 OK`):

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

Cada entrada tiene la forma del propio [`CalendarListEntry`](https://developers.google.com/calendar/api/v3/reference/calendarList) de Google, por lo que los nombres de los campos siguen el `camelCase` de Google, no el `snake_case` habitual de esta API; es decir, son los datos de Google transmitidos tal cual, no los nuestros. Una conexión faltante o revocada devuelve un `400` con un error que explica que Google Calendar debe ser (re)conectado.

---

## Importar eventos desde un Google Calendar

`POST /appointments/import-calendar-events`

Extrae los eventos que ya se encuentran en los Google Calendar conectados de una campaña o un Agente de IA y los convierte en citas; es útil la primera vez que conecta un calendario que ya tiene reservas. Esto puede llevar tiempo (cada evento pasa por un proceso de extracción para determinar a quién pertenece), por lo que nunca se ejecuta en línea: la solicitud pone en cola un trabajo en segundo plano y le devuelve un `job_id` para realizar consultas.

| Campo | Obligatorio | Descripción |
|---|---|---|
| `campaign_id` | Uno de estos dos | La campaña desde cuyos calendarios conectados importar. |
| `agent_id` | Uno de estos dos | El Agente de IA desde cuyos calendarios conectados importar. |
| `identifier` | Sí | `"EMAIL"` o `"PHONE_NUMBER"`: qué pieza de información de contacto extraer de cada evento del calendario para buscar o crear el contacto al que pertenece. |

Envíe exactamente uno de `campaign_id` / `agent_id`, nunca ambos y nunca ninguno; cualquier otra combinación devuelve un `400`. Cualquiera que envíe debe pertenecer a su cuenta, o recibirá 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"])
```

**Respuesta** (`202 Accepted`):

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

`campaign_id` y `agent_id` devuelven el valor que usted envió; el otro es siempre `null`.

### Consultar el trabajo de importación

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

**Respuesta** (`200 OK`):

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

| `status` | Significado |
|---|---|
| `queued` | Aún no se ha procesado. Siga consultando. |
| `processing` | La importación está en curso. Siga consultando. |
| `completed` | Finalizado: `message` contiene un breve resumen legible para humanos. |
| `failed` | Algo salió mal: `error` contiene el motivo. |

`GET` en un `jobId` que no existe (o que pertenece a una cuenta diferente) devuelve un `404`.

---

## Integraciones de reservas de restaurantes (Zenchef / Formitable)

Zenchef y Formitable son sistemas de reserva de restaurantes a través de los cuales su Agente de IA puede reservar mesas reales. Cada uno tiene un **widget de reserva público y sin autenticar** (`https://api.youraiconnector.com/v1/zenchef-widget/...` y `https://api.youraiconnector.com/v1/formitable-widget/...`) que se muestra dentro del chat para el comensal; esas rutas del widget son páginas HTML simples destinadas a abrirse en un navegador, no puntos finales de API JSON, por lo que no están documentadas aquí. A continuación, se presentan los puntos finales de gestión de cuentas: verificar que un ID de restaurante pertenece al titular de la cuenta y, posteriormente, añadirlo, actualizarlo o eliminarlo.

### Zenchef

Conectar un restaurante de Zenchef es una verificación de dos pasos, por lo que el titular de la cuenta demuestra que realmente dirige el restaurante antes de que se conecte al bot: primero se comprueba que el ID existe (sin revelar el nombre) y, a continuación, se le pide que escriba el nombre del restaurante para verificar que coincide.

**Paso 1 — Comprobar que existe un ID de restaurante**

`POST /appointments/zenchef-restaurants/check`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_id` | Sí | El ID del restaurante Zenchef que se va a comprobar. |

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

**Respuesta** (`200 OK`):

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

`exists: false` significa que ningún restaurante de Zenchef tiene ese ID; no hay nada más que hacer. Limitado a 10 comprobaciones por cada 5 minutos por cuenta; exceder este límite devuelve `429`.

**Paso 2 — Verificar el nombre del restaurante**

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

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_id` | Sí | El ID del restaurante Zenchef del paso 1. |
| `user_input_name` | Sí | El nombre que escribió el titular de la cuenta, comparado con el nombre real del restaurante en Zenchef (sin distinguir entre mayúsculas y minúsculas ni espacios en blanco). |

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

**Respuesta** (`200 OK`):

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

`verified: false` significa que el nombre no coincidió; `restaurantDetails` se omite, pida al titular de la cuenta que lo intente de nuevo. Limitado a 3 intentos cada 5 minutos (más estricto que la comprobación de existencia, ya que este es el paso de prueba real). Un `restaurant_id` que ya no se resuelve en Zenchef devuelve `404`.

**Paso 3 — Guardar el restaurante**

`POST /appointments/zenchef-restaurants`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_id` | Sí | 1–64 caracteres, letras/números/guion bajo/guion. |
| `restaurant_name` | Sí | El nombre del restaurante verificado del paso 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" }'
```

**Respuesta** (`201 Created`):

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

**Actualizar un restaurante Zenchef guardado**

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

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_name` | No | Nuevo nombre de visualización. |
| `is_active` | No | Establezca `false` para evitar que el bot realice reservas en este restaurante sin eliminarlo. |

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

**Respuesta** (`200 OK`): misma estructura que la respuesta de guardado anterior.

**Eliminar un restaurante Zenchef**

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

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

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

Un `restaurantId` que no esté actualmente en la cuenta devuelve `404` al intentar actualizar o eliminar.

### Formitable

Formitable no necesita la prueba de nombre de dos pasos que requiere Zenchef; sus ID de restaurante ya están definidos por negocio, por lo que una llamada de verificación es suficiente. También cuenta con una búsqueda de detalles que se utiliza para almacenar en caché la URL del sitio web del restaurante durante la configuración.

**Verificar un ID de restaurante**

`POST /appointments/formitable-restaurants/verify`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_id` | Sí | El ID de restaurante de Formitable. |
| `language` | No | Etiqueta de idioma para la solicitud de sondeo. El valor predeterminado es `"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" }'
```

**Respuesta** (`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 no reconoce devuelve `404`. Limitado a 10 intentos por cada 5 minutos por cuenta.

**Obtener detalles del restaurante**

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

Obtiene el perfil público del restaurante desde Formitable, incluida su página web; se utiliza para almacenar en caché la URL del sitio web mientras se configura el restaurante. `language` es un parámetro de consulta opcional, cuyo valor predeterminado es `"en"`.

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

**Respuesta** (`200 OK`):

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

**Guardar el restaurante**

`POST /appointments/formitable-restaurants`

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_id` | Sí | 1–64 caracteres, letras/números/guion bajo/guion. |
| `restaurant_name` | Sí | Nombre para mostrar. |
| `language` | Sí | Etiqueta de idioma ISO, p. ej., `"en"` o `"en-GB"`. |
| `website_url` | No | El sitio web del restaurante, obtenido de la búsqueda de detalles anterior. Debe ser `http(s)://`. |

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

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

**Actualizar un restaurante Formitable guardado**

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

| Campo | Obligatorio | Descripción |
|---|---|---|
| `restaurant_name` | No | Nuevo nombre para mostrar. |
| `language` | No | Nueva etiqueta de idioma ISO. |
| `is_active` | No | Establezca `false` para evitar que el bot realice reservas en este restaurante sin eliminarlo. |
| `website_url` | No | Nueva URL del sitio 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 }'
```

**Respuesta** (`200 OK`): misma estructura que la respuesta de guardado anterior.

**Eliminar un restaurante Formitable**

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

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

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

Un `restaurantId` que no esté actualmente en la cuenta devuelve `404` al intentar actualizar o eliminar.

> **Estructura de error en todos los endpoints de Zenchef/Formitable:** a diferencia del resto de esta página, los errores aquí incluyen su estado dos veces (una como estado HTTP y otra como `error_code` en el cuerpo), por ejemplo `{ "success": false, "error": "Restaurant not found", "error_code": 404 }`. Manéjelo de la misma manera que cualquier otro error: verifique `success` y lea `error` para obtener el mensaje.

---

## Errores de la API de citas

Los endpoints de citas devuelven el sobre de error estándar:

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

| Estado | Cuándo ocurre en un endpoint de citas |
|---|---|
| `400` | Falta un campo obligatorio o no es válido; por ejemplo, un `start_time` incorrecto, una `end_time` que no es posterior a `start_time`, una combinación de filtros no válida, no hay campos para actualizar o una cita ya cancelada. |
| `404` | No se encontró la cita, el contacto o el tipo de evento. |
| `409` | La franja horaria solicitada ya está ocupada (conflicto de reserva). |

Los códigos compartidos que puede devolver cualquier endpoint — `401`, `403` (su plan no incluye acceso a la API), `429` (límite de tasa) y `500` — se enumeran con orientación sobre reintentos en [Errores y paginación](errors-and-pagination.md).

---

## Próximos pasos

- [Contactos](contacts.md) — cree y busque los contactos para los que realiza reservas.
- [Mensajes y conversaciones](messages.md) — envíe a un contacto una confirmación o un recordatorio.
- [Webhooks](webhooks.md) — reciba notificaciones cuando cambien las citas.
