Citas
La API de Citas le permite reservar citas para sus contactos en sus tipos de eventos, y luego 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, Formitable, OpenTable o TheFork 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; las empresas de citas que gestionan su agenda en Trafft pueden conectarse de la misma manera.
Todas las rutas en esta página son relativas a la URL base https://api.dmchamp.com/v1. Cada solicitud requiere su clave de API; consulte Autenticación 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_idpuede seguir siendonullycalendar_syncedpuede serfalseporque 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.
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_timeyend_timeson 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 espera un instante UTC en formato ISO 8601, así que convierta el espacio que eligió antes de enviarlo.
cURL
curl "https://api.dmchamp.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
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.dmchamp.com/v1/appointments/available-slots?${params}`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.data);
Python
import requests
res = requests.get(
"https://api.dmchamp.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):
{
"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=)
curl -X POST "https://api.dmchamp.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
const res = await fetch("https://api.dmchamp.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
import requests
res = requests.post(
"https://api.dmchamp.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):
{
"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
curl "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointment);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["appointment"])
Respuesta (200 OK):
{
"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_idpara ver las citas confirmadas de un contacto. Puede restringir esto a un solo día pasando tambiéndate. - Por estado: establezca
status(sincontact_id) para listar solo las citasConfirmedo solo lasCanceleden toda la cuenta. - El filtro
datesincontact_id, ostatus=Canceledjunto concontact_id, devuelve un400.
cURL
curl "https://api.dmchamp.com/v1/appointments?contact_id=contact_abc123&date=2026-06-15" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const params = new URLSearchParams({
contact_id: "contact_abc123",
date: "2026-06-15",
});
const res = await fetch(
`https://api.dmchamp.com/v1/appointments?${params}`,
{ headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.appointments, data.next_cursor);
Python
import requests
res = requests.get(
"https://api.dmchamp.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):
{
"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 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
curl -X PUT "https://api.dmchamp.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
const res = await fetch(
"https://api.dmchamp.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
import requests
res = requests.put(
"https://api.dmchamp.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):
{
"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
curl -X POST "https://api.dmchamp.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
const res = await fetch(
"https://api.dmchamp.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
import requests
res = requests.post(
"https://api.dmchamp.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):
{
"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 en su lugar.
cURL
curl -X DELETE "https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
{ method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();
console.log(data.success);
Python
import requests
res = requests.delete(
"https://api.dmchamp.com/v1/appointments/aBcD1234eFgH5678",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["success"])
Respuesta (200 OK):
{
"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
curl "https://api.dmchamp.com/v1/appointments/google-calendars" \
-H "X-API-Key: YOUR_API_KEY"
JavaScript
const res = await fetch("https://api.dmchamp.com/v1/appointments/google-calendars", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();
console.log(data.data);
Python
import requests
res = requests.get(
"https://api.dmchamp.com/v1/appointments/google-calendars",
headers={"X-API-Key": "YOUR_API_KEY"},
)
print(res.json()["data"])
Respuesta (200 OK):
{
"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 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
curl -X POST "https://api.dmchamp.com/v1/appointments/import-calendar-events?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent_abc123",
"identifier": "EMAIL"
}'
JavaScript
const res = await fetch("https://api.dmchamp.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
import requests
res = requests.post(
"https://api.dmchamp.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):
{
"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}
curl "https://api.dmchamp.com/v1/appointments/import-calendar-events/jK9mQ2xR7pL4wN1t" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK):
{
"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 reserva externas (Zenchef / Formitable / OpenTable / TheFork / Trafft)
Zenchef y Formitable son sistemas de reserva de restaurantes a través de los cuales su Agente de IA puede reservar mesas reales; Trafft es una plataforma de programación para empresas de citas, que se conecta una vez por cuenta en lugar de por restaurante. Las dos plataformas de restaurantes tienen cada una un widget de reserva público y sin autenticación (https://api.dmchamp.com/v1/zenchef-widget/... y https://api.dmchamp.com/v1/formitable-widget/...) que se muestra dentro del chat para el comensal; esas rutas de 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, luego, 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. |
curl -X POST "https://api.dmchamp.com/v1/appointments/zenchef-restaurants/check?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "12345" }'
Respuesta (200 OK):
{
"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). |
curl -X POST "https://api.dmchamp.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):
{
"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. |
curl -X POST "https://api.dmchamp.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):
{ "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. |
curl -X PUT "https://api.dmchamp.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}
curl -X DELETE "https://api.dmchamp.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". |
curl -X POST "https://api.dmchamp.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):
{
"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".
curl "https://api.dmchamp.com/v1/appointments/formitable-restaurants/the-blue-door/details?language=en" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK):
{
"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)://. |
curl -X POST "https://api.dmchamp.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. |
curl -X PUT "https://api.dmchamp.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}
curl -X DELETE "https://api.dmchamp.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.
OpenTable
Se accede a los restaurantes de OpenTable a través de las credenciales de socio de OpenTable de la plataforma, y OpenTable solo permite que esas credenciales vean los restaurantes que conectaron el listado de la plataforma dentro del Marketplace de Integraciones de OpenTable. Por lo tanto, al igual que con Formitable, una llamada de verificación es suficiente: un ID de restaurante accesible (el “RID” numérico) demuestra tanto que el restaurante existe como que ha conectado la integración. Hasta que el listado de socio de OpenTable esté habilitado en la plataforma, la llamada de verificación responderá 503.
Verificar un restaurante de OpenTable
POST /appointments/opentable-restaurants/verify
| Campo | Obligatorio | Descripción |
|---|---|---|
restaurant_id |
Sí | El ID de restaurante de OpenTable (RID), un número como 1038007. |
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants/verify?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "1038007" }'
Respuesta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"restaurantId": "1038007",
"diningAreaCount": 2,
"diningAreaNames": ["Main Room", "Garden"],
"tableTypes": ["default", "outdoor", "bar"]
}
}
}
verified: false significa que ningún restaurante de OpenTable tiene ese ID. Un 403 significa que el restaurante existe pero aún no ha conectado la integración de la plataforma dentro de OpenTable. Limitado a 10 intentos por cada 5 minutos por cuenta.
Añadir un restaurante de OpenTable
POST /appointments/opentable-restaurants
| Campo | Obligatorio | Descripción |
|---|---|---|
restaurant_id |
Sí | El ID de restaurante verificado. |
restaurant_name |
Sí | Nombre para mostrar (una etiqueta; también es como la IA llama al restaurante). |
website_url |
No | Una URL http(s) que se muestra a los invitados cuando la IA los deriva al restaurante. |
curl -X POST "https://api.dmchamp.com/v1/appointments/opentable-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "1038007", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
Respuesta (201 Created): { "success": true, "data": { "restaurantId": "1038007" } }
Actualizar un restaurante de OpenTable guardado
PUT /appointments/opentable-restaurants/{restaurantId}
Envíe cualquiera de restaurant_name, is_active (pausar con false) o website_url (una cadena vacía lo borra); los campos omitidos se dejan sin cambios.
curl -X PUT "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Respuesta (200 OK): { "success": true, "data": { "restaurantId": "1038007" } }
Eliminar un restaurante de OpenTable
DELETE /appointments/opentable-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/opentable-restaurants/1038007" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK): { "success": true, "data": { "restaurantId": "1038007" } }
Un restaurantId que no se encuentra actualmente en la cuenta devuelve 404 al actualizar.
TheFork
Se accede a los restaurantes de TheFork a través de las credenciales de socio de TheFork de la plataforma, y TheFork solo permite que esas credenciales vean los restaurantes que tienen habilitado al socio de la plataforma en su cuenta de TheFork. Por lo tanto, al igual que con Formitable y OpenTable, una llamada de verificación es suficiente: un ID de restaurante accesible demuestra tanto que el restaurante existe como que el socio está habilitado en él. El ID es el UUID que TheFork asigna al restaurante en TheFork Manager, enviado como una cadena de texto. Hasta que TheFork haya aprobado a la plataforma como socio y emitido las credenciales, la llamada de verificación responderá 503; consulte TheFork para saber qué significa esto hoy en día.
Verificar un restaurante de TheFork
POST /appointments/thefork-restaurants/verify
| Campo | Obligatorio | Descripción |
|---|---|---|
restaurant_id |
Sí | El ID de restaurante de TheFork, un UUID como 9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60. |
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants/verify?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" }'
Respuesta (200 OK):
{
"success": true,
"data": {
"verified": true,
"restaurantDetails": {
"restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60",
"partySizes": [1, 2, 3, 4, 5, 6, 7, 8]
}
}
}
partySizes son los tamaños de grupo que el restaurante acepta en línea durante los próximos 30 días. Un 404 o 403 significa que TheFork no nos proporcionó un restaurante con ese ID; ya sea porque el ID es incorrecto o porque el socio de la plataforma aún no está habilitado en ese restaurante; un 400 significa que el ID no es un UUID. Limitado a 10 intentos por cada 5 minutos por cuenta.
Añadir un restaurante de TheFork
POST /appointments/thefork-restaurants
| Campo | Obligatorio | Descripción |
|---|---|---|
restaurant_id |
Sí | El ID de restaurante verificado (UUID). |
restaurant_name |
Sí | Nombre para mostrar (una etiqueta; también es cómo la IA llama al restaurante). |
website_url |
No | Una URL http(s) que se muestra a los invitados cuando la IA los deriva al restaurante. |
curl -X POST "https://api.dmchamp.com/v1/appointments/thefork-restaurants?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "restaurant_id": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60", "restaurant_name": "The Blue Door", "website_url": "https://thebluedoor.example" }'
Respuesta (201 Created): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Actualizar un restaurante de TheFork guardado
PUT /appointments/thefork-restaurants/{restaurantId}
Envíe cualquiera de restaurant_name, is_active (pausar con false) o website_url (una cadena vacía lo borra); los campos omitidos se dejan sin cambios.
curl -X PUT "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Respuesta (200 OK): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Eliminar un restaurante de TheFork
DELETE /appointments/thefork-restaurants/{restaurantId}
curl -X DELETE "https://api.dmchamp.com/v1/appointments/thefork-restaurants/9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK): { "success": true, "data": { "restaurantId": "9f2a1c34-5b6d-4e7f-8a90-1b2c3d4e5f60" } }
Un restaurantId que no esté actualmente en la cuenta devuelve 404 al intentar actualizar o eliminar.
Trafft
Trafft se conecta una vez para toda la cuenta, no por ubicación: una dirección de empresa más las credenciales de API del panel de administración de Trafft (Features & Integrations → API & Connectors, parte del plan Business de Trafft). La llamada de conexión verifica esas credenciales con Trafft antes de almacenar nada, por lo que una dirección incorrecta, credenciales erróneas o un plan sin acceso a la API fallarán aquí en lugar de durante una conversación con el cliente. El secreto del cliente se almacena cifrado y nunca se devuelve mediante ningún punto final.
Las tres llamadas de escritura (POST, PUT, DELETE) requieren el permiso de edición de Integraciones; el estado GET requiere el permiso de visualización de Integraciones.
Conectar Trafft
POST /appointments/trafft/connect
| Campo | Obligatorio | Descripción |
|---|---|---|
subdomain |
Sí | La dirección de la empresa: la parte anterior a .admin.trafft.com en la URL en la que inicia sesión, p. ej. acme. Se acepta una dirección completa y se reduce al mismo valor. |
client_id |
Sí | ID de cliente de la página API & Connectors de Trafft. |
client_secret |
Sí | Secreto de cliente de la misma página. Se almacena cifrado, nunca se devuelve. |
company_name |
No | Una etiqueta para su propia lista. |
curl -X POST "https://api.dmchamp.com/v1/appointments/trafft/connect?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "acme",
"client_id": "YOUR_TRAFFT_CLIENT_ID",
"client_secret": "YOUR_TRAFFT_CLIENT_SECRET",
"company_name": "Acme Salon"
}'
Respuesta (200 OK):
{
"success": true,
"data": {
"subdomain": "acme",
"service_count": 12,
"employee_count": 4,
"location_count": 2
}
}
Los recuentos se devuelven desde Trafft durante la verificación; son la forma más rápida de confirmar que las credenciales apuntan a la cuenta que usted pretendía.
Obtener el estado de la conexión
GET /appointments/trafft
curl "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK):
{
"success": true,
"data": {
"connected": true,
"subdomain": "acme",
"hostname": "acme.admin.trafft.com",
"client_id": "YOUR_TRAFFT_CLIENT_ID",
"company_name": "Acme Salon",
"is_active": true,
"service_count": 12,
"employee_count": 4,
"location_count": 2
}
}
connected: false significa que aún no se ha configurado nada. El secreto del cliente nunca se incluye en esta respuesta.
Actualizar la conexión
PUT /appointments/trafft
| Campo | Obligatorio | Descripción |
|---|---|---|
is_active |
No | Establezca false en pausar: la IA deja de realizar reservas en Trafft, la conexión permanece. true la reanuda. |
company_name |
No | Nueva etiqueta. |
curl -X PUT "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "is_active": false }'
Respuesta (200 OK): el mismo objeto de conexión que el GET anterior.
Desconectar Trafft
DELETE /appointments/trafft
curl -X DELETE "https://api.dmchamp.com/v1/appointments/trafft" \
-H "X-API-Key: YOUR_API_KEY"
Respuesta (200 OK): { "success": true }
La desconexión solo elimina las credenciales almacenadas. Las citas que ya están en Trafft permanecen intactas.
Formato de error en todos los endpoints de Zenchef/Formitable/OpenTable/TheFork: a diferencia del resto de esta página, los errores aquí incluyen el estado dos veces (una vez como estado HTTP y otra como
error_codeen el cuerpo); por ejemplo,{ "success": false, "error": "Restaurant not found", "error_code": 404 }. Manéjelo de la misma manera que cualquier otro error: verifiquesuccessy leaerrorpara obtener el mensaje.
Errores de la API de citas
Los endpoints de citas devuelven el sobre de error estándar:
{
"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.
Utilice su propio cliente OAuth de Google (pantalla de consentimiento de Calendar)
Cuando una cuenta conecta Google Calendar, la ventana de inicio de sesión de Google nombra el proyecto del cliente OAuth (por defecto, el de la plataforma). Una agencia puede registrar su propio cliente OAuth 2.0 de Google en la cuenta de la agencia; a partir de ese momento, la conexión de calendario para esa cuenta y todas las subcuentas bajo ella se ejecuta a través de ese cliente, por lo que la pantalla de consentimiento muestra el nombre y el logotipo de la agencia. Nada más cambia: el flujo de conexión, la sincronización bidireccional y los endpoints de citas anteriores funcionan exactamente igual que antes.
Solo para Google Calendar. El OAuth del buzón de Gmail para el canal de correo electrónico no se ve afectado.
Lo que su cliente necesita primero
- Un cliente OAuth 2.0 de tipo Aplicación web en su proyecto de Google Cloud, con la API de Google Calendar habilitada en ese proyecto.
- Cada entrada
redirect_uris(devuelta por los endpoints a continuación) agregada en los URI de redireccionamiento autorizados del cliente. La primera entrada es su dominioapi.verificado cuando tiene uno (Google solo verifica una marca cuyo redireccionamiento reside en un dominio de su propiedad), seguido del host neutral de la plataforma como alternativa utilizada hasta entonces. - La pantalla de consentimiento con su marca, su dominio en Dominios autorizados y los dos alcances de Calendar declarados (
scopesen la respuesta). Hasta que la aplicación sea publicada y verificada por Google, los usuarios verán una advertencia de aplicación no verificada y el cliente estará limitado a 100 usuarios.
Guarde su cliente
PUT /account-config/google-oauth-client
| Campo | Obligatorio | Descripción |
|---|---|---|
client_id |
Sí | El ID de cliente OAuth 2.0, que termina en .apps.googleusercontent.com. |
client_secret |
Sí | El secreto del cliente. Se verifica con Google antes de almacenarse y luego se cifra. Nunca es devuelto por ningún endpoint. |
curl -X PUT "https://api.dmchamp.com/v1/account-config/google-oauth-client?apiKey=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
"client_secret": "GOCSPX-your-client-secret"
}'
Respuesta
{
"success": true,
"configured": true,
"client_id": "123456789012-abcdefghijklmnop.apps.googleusercontent.com",
"redirect_uris": [
"https://api.youragency.com/v1/auth-google-callback",
"https://api.dmchamp.com/v1/auth-google-callback"
],
"scopes": [
"https://www.googleapis.com/auth/calendar.events",
"https://www.googleapis.com/auth/calendar.readonly"
],
"setup": ["…"]
}
Un secreto incorrecto o un ID de cliente desconocido se rechaza con 400 y el motivo propio de Google en error, y no se almacena nada.
Leer o eliminar
GET /account-config/google-oauth-client devuelve el mismo resumen en cualquier momento: configured: false además de redirect_uris y scopes antes de que se guarde nada, por lo que puede configurar primero el lado de Google. DELETE /account-config/google-oauth-client elimina el cliente: las nuevas conexiones vuelven al cliente de la plataforma, y los calendarios que se conectaron a través del cliente eliminado deben volver a conectarse, porque solo el cliente que emitió una conexión puede actualizarla.
Los miembros del equipo necesitan Integraciones: ver para GET y Integraciones: editar para PUT / DELETE.
Próximos pasos
- Contactos — cree y busque los contactos para los que realiza reservas.
- Mensajes y conversaciones — envíe a un contacto una confirmación o un recordatorio.
- Webhooks — reciba notificaciones cuando cambien las citas.