{"openapi":"3.1.0","info":{"title":"DevRoam AI API","version":"1.0.0","description":"API REST de DevRoam AI para conectar Roa con tu CRM, tu web, Zapier o Make. Autenticación con API key (Ajustes → API y webhooks). Las horas de citas (`start_local`, `from`, `to`, `date`) son hora local del negocio (`YYYY-MM-DD HH:MM`); las marcas de registro (`created_at`, `updated_at`, `started_at`, `ended_at`, `last_call_at`) son UTC en ISO 8601. La antelación mínima del negocio se aplica a la API; el límite de días vista (max_days) no, porque la API actúa en nombre del propio negocio. Errores 409 con code `busy` (agenda bloqueada un instante por otra reserva: reintentar tras Retry-After), `slot_unavailable`, `party_too_large` o `appointment_not_active`; 405 `method_not_allowed`; 413 `payload_too_large` (cuerpo > 100 KB). Límite: 120 peticiones por minuto y clave. Incluida en los planes Profesional y Avanzado."},"servers":[{"url":"https://app.devroam.com"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"API key `dr_live_…`"}},"schemas":{"Appointment":{"type":"object","properties":{"id":{"type":"integer"},"customer_name":{"type":"string"},"customer_phone":{"type":["string","null"]},"service":{"type":["string","null"]},"notes":{"type":["string","null"]},"status":{"type":"string","enum":["confirmed","pending_payment","cancelled"],"description":"pending_payment: negocio con señal online; el cliente recibe el enlace de pago y la cita se confirma al pagar (si no paga en plazo, según la configuración del negocio se anula o queda pendiente)"},"staff_id":{"type":["integer","null"]},"staff_name":{"type":["string","null"]},"party_size":{"type":["integer","null"]},"area":{"type":["string","null"]},"start_local":{"type":"string","example":"2026-11-03 17:30"},"manage_url":{"type":"string"},"customer_note":{"type":["string","null"],"description":"Nota que deja el cliente desde su enlace de gestión"},"call_id":{"type":["string","null"],"description":"Llamada en la que se reservó, si la hubo"},"created_at":{"type":"string","description":"UTC, ISO 8601"},"updated_at":{"type":"string","description":"UTC, ISO 8601"}}},"NewAppointment":{"type":"object","required":["customer_name","start_local"],"properties":{"customer_name":{"type":"string"},"customer_phone":{"type":"string"},"service":{"type":"string"},"staff_id":{"type":"integer"},"notes":{"type":"string"},"party_size":{"type":"integer","description":"Restaurantes: número de comensales"},"area":{"type":"string","description":"Restaurantes: zona (p. ej. terraza)"},"start_local":{"type":"string"},"notify_customer":{"type":"boolean","default":true,"description":"false: no se avisa al cliente. En negocios con señal online la cita se crea confirmada y sin señal (el cliente no recibiría el enlace de pago)"}}},"Call":{"type":"object","properties":{"id":{"type":"string"},"channel":{"type":"string"},"caller":{"type":["string","null"]},"started_at":{"type":"string"},"ended_at":{"type":["string","null"]},"duration_sec":{"type":["integer","null"]},"outcome":{"type":["string","null"]},"summary":{"type":["string","null"]}}},"Customer":{"type":"object","properties":{"id":{"type":"integer"},"phone":{"type":"string"},"name":{"type":["string","null"]},"notes":{"type":["string","null"]},"vip":{"type":"integer"},"blocked":{"type":"integer"},"visits":{"type":"integer"},"calls":{"type":"integer"},"last_call_at":{"type":["string","null"]}}},"Service":{"type":"object","properties":{"name":{"type":"string"},"price":{"type":"string"},"minutes":{"type":"integer"},"buffer":{"type":"integer"}}},"Message":{"type":"object","properties":{"id":{"type":"integer"},"caller_name":{"type":["string","null"]},"caller_phone":{"type":["string","null"]},"message":{"type":"string"},"urgent":{"type":"integer"},"created_at":{"type":"string"}}},"WebhookEvent":{"type":"object","description":"Cuerpo de cada webhook. Firma: cabecera `DevRoam-Signature: t=<unix>,v1=<hex>` donde v1 = HMAC-SHA256(secreto, `${t}.${cuerpo}`). Reintentos: 1 min, 5 min, 30 min, 2 h y 6 h si no respondes 2xx en 8 s.","properties":{"id":{"type":"string"},"type":{"type":"string","enum":["booking.created","booking.changed","booking.cancelled","call.completed","message.received","test.ping"]},"created":{"type":"string"},"business_id":{"type":"string"},"data":{"type":"object"}}}}},"paths":{"/api/v1/business":{"get":{"summary":"Datos del negocio","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"sector":{"type":["string","null"]},"timezone":{"type":"string"},"language":{"type":"string"},"address":{"type":["string","null"]},"contact_phone":{"type":["string","null"]},"hours":{},"holidays":{}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/services":{"get":{"summary":"Servicios (nombre, precio, duración, margen)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/staff":{"get":{"summary":"Equipo","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/availability":{"get":{"summary":"Huecos libres de un día","parameters":[{"name":"date","in":"query","required":true,"description":"YYYY-MM-DD","schema":{"type":"string"}},{"name":"staff_id","in":"query","required":false,"description":"Profesional","schema":{"type":"integer"}},{"name":"service","in":"query","required":false,"description":"Servicio (para usar su duración)","schema":{"type":"string"}},{"name":"party_size","in":"query","required":false,"description":"Restaurantes: comensales","schema":{"type":"integer"}},{"name":"area","in":"query","required":false,"description":"Restaurantes: zona","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string"},"slots":{"type":"array","items":{"type":"string","example":"10:30"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/appointments":{"get":{"summary":"Listar citas/reservas","parameters":[{"name":"from","in":"query","required":false,"description":"YYYY-MM-DD (por defecto hoy en la zona horaria del negocio)","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"YYYY-MM-DD, incluido (todo ese día)","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"confirmed | pending_payment | cancelled","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-200","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}},"post":{"summary":"Crear cita/reserva","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewAppointment"}}}},"responses":{"201":{"description":"Creada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"400":{"description":"Parámetro no válido: fecha imposible, staff_id inexistente, party_size ausente en restaurantes"},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"409":{"description":"Hueco no disponible (slot_unavailable), grupo mayor que el máximo online (party_too_large) o agenda ocupada un instante (busy, con Retry-After)"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/appointments/{id}":{"get":{"summary":"Ver cita","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"404":{"description":"No existe"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}},"patch":{"summary":"Cambiar cita (hora, datos, notas, profesional, comensales, zona)","description":"Mismos campos que al crear. notify_customer:false evita el aviso al cliente si cambia la hora.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewAppointment"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"409":{"description":"Hueco no disponible, o la cita ya no está activa (code appointment_not_active: cancelada); no se cambia nada ni se avisa a nadie"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}},"delete":{"summary":"Cancelar cita","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/calls":{"get":{"summary":"Últimas llamadas","parameters":[{"name":"limit","in":"query","required":false,"description":"1-200","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Call"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/calls/{id}":{"get":{"summary":"Llamada con transcripción","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Call"}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/customers":{"get":{"summary":"Clientes (llamantes conocidos)","parameters":[{"name":"q","in":"query","required":false,"description":"Buscar por nombre o teléfono","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"1-200","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}},"/api/v1/messages":{"get":{"summary":"Recados","parameters":[{"name":"limit","in":"query","required":false,"description":"1-200","schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Message"}}}}}}},"401":{"description":"API key ausente o no válida"},"403":{"description":"Plan sin API o suscripción inactiva"},"429":{"description":"Límite de 120 peticiones/minuto superado (cabecera Retry-After)"}}}}},"webhooks":{"booking.created":{"post":{"summary":"booking.created","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Responde 2xx para confirmar"}}}},"booking.changed":{"post":{"summary":"booking.changed","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Responde 2xx para confirmar"}}}},"booking.cancelled":{"post":{"summary":"booking.cancelled","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Responde 2xx para confirmar"}}}},"call.completed":{"post":{"summary":"call.completed","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Responde 2xx para confirmar"}}}},"message.received":{"post":{"summary":"message.received","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"Responde 2xx para confirmar"}}}}}}