{"openapi":"3.1.0","info":{"title":"BloomBooking API","version":"1.0.0","summary":"Disponibilidad, citas y clientes de un centro de fisioterapia o belleza.","description":"API REST del centro. Es la base de todas las conectividades de\nBloomBooking: la web pública de reservas, el servidor MCP, los agentes\nde voz y de WhatsApp y las integraciones del propio centro hablan con\neste mismo motor.\n\n## Autenticación\n\nCada centro emite sus claves desde *Ajustes → API*. Se manda en cada\npetición como `Authorization: Bearer bb_…` (o `X-API-Key`). Una clave\npertenece a un solo centro y lleva permisos explícitos, así que una\nintegración de sólo lectura no puede anular citas aunque quiera.\n\nLa clave se enseña **una vez**, al crearla: después sólo vive su hash.\nSi se pierde, se revoca y se emite otra.\n\n## Horas y fechas\n\nLos instantes van en ISO 8601 con offset (`2026-09-01T09:00:00.000Z`).\nLos rangos de días (`from`, `to`) son **días civiles del centro**, y\nlas respuestas traen además la hora de pared del local y su zona\nIANA: quien integra no tiene por qué saber que Canarias no empieza el\ndía a la misma hora que Madrid.\n\n## Errores\n\nSiempre `{ \"error\": { \"code\", \"message\", \"details?\" } }` con el código\nestable en `code`. El 409 al reservar (`slot_taken`) no es un fallo de\nla integración: es que alguien ha cogido ese hueco primero.\n\n## Límite de uso\n\n120 peticiones por minuto y clave. Al pasarse, 429 con `rate_limited`.","contact":{"name":"BloomBooking","url":"https://www.bloombooking.com"}},"servers":[{"url":"https://api.bloombooking.com/api/v1","description":"Producción"}],"paths":{"/me":{"get":{"operationId":"getMe","summary":"Quién soy","description":"El centro al que pertenece la clave y los permisos que lleva.\n\nNo requiere permisos concretos, sólo una clave válida.","tags":["Cuenta"],"x-required-scopes":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/locations":{"get":{"operationId":"listLocations","summary":"Locales del centro","description":"Cada local trae su zona horaria: las horas de pared se derivan de ahí.\n\nPermisos: `catalog:read`.","tags":["Catálogo"],"x-required-scopes":["catalog:read"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LocationList"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/services":{"get":{"operationId":"listServices","summary":"Servicios","description":"El catálogo completo del centro. Con `bookable_online=true` sólo lo que el centro publica en su web pública de reservas.\n\nPermisos: `catalog:read`.","tags":["Catálogo"],"x-required-scopes":["catalog:read"],"parameters":[{"name":"location_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"bookable_online","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceList"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/professionals":{"get":{"operationId":"listProfessionals","summary":"Profesionales","description":"Con `location_id`, sólo quien tiene horario en esa sede.\n\nPermisos: `catalog:read`.","tags":["Catálogo"],"x-required-scopes":["catalog:read"],"parameters":[{"name":"location_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfessionalList"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/availability":{"get":{"operationId":"getAvailability","summary":"Huecos libres de un servicio","description":"El mismo motor que la agenda del centro: horario real del profesional × recursos libres, con el quantum y el margen entre citas del centro. La sala y el aparato no se eligen aquí: los asigna el sistema al reservar.\n\nPermisos: `availability:read`.","tags":["Disponibilidad"],"x-required-scopes":["availability:read"],"parameters":[{"name":"service_id","in":"query","required":true,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"from","in":"query","required":true,"description":"Fecha civil del centro, `YYYY-MM-DD`","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"to","in":"query","required":true,"description":"Fecha civil del centro, `YYYY-MM-DD`","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"professional_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"limit","in":"query","required":false,"schema":{"default":200,"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Availability"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clients":{"get":{"operationId":"listClients","summary":"Buscar clientes","description":"Por texto libre (`q`), por teléfono (en cualquier formato: se normaliza a E.164 antes de buscar) o por correo.\n\nPermisos: `clients:read`.","tags":["Clientes"],"x-required-scopes":["clients:read"],"parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","maxLength":120}},{"name":"phone","in":"query","required":false,"schema":{"type":"string","maxLength":40}},{"name":"email","in":"query","required":false,"schema":{"type":"string","maxLength":200}},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":9007199254740991}},{"name":"page_size","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientList"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createClient","summary":"Crear cliente","description":"Si ya hay una ficha con ese teléfono o ese correo, se devuelve esa: duplicar la ficha de una clienta habitual no es una opción.\n\nPermisos: `clients:write`.","tags":["Clientes"],"x-required-scopes":["clients:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientInput"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientEnvelope"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado: el hueco acaba de ocuparse (`slot_taken`), la cita ya estaba anulada, etc. El código concreto va en `error.details.code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/clients/{id}":{"get":{"operationId":"getClient","summary":"Ficha de un cliente","description":"Permisos: `clients:read`.","tags":["Clientes"],"x-required-scopes":["clients:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientEnvelope"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe en este centro (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/appointments":{"get":{"operationId":"listAppointments","summary":"Citas de un rango de días","description":"`from` y `to` son días civiles del centro, no instantes.\n\nPermisos: `appointments:read`.","tags":["Citas"],"x-required-scopes":["appointments:read"],"parameters":[{"name":"from","in":"query","required":true,"description":"Fecha civil del centro, `YYYY-MM-DD`","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"to","in":"query","required":true,"description":"Fecha civil del centro, `YYYY-MM-DD`","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"location_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"client_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"professional_id","in":"query","required":false,"schema":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","confirmed","in_progress","completed","no_show","cancelled"]}},{"name":"page","in":"query","required":false,"schema":{"default":1,"type":"integer","minimum":1,"maximum":9007199254740991}},{"name":"page_size","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentList"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createAppointment","summary":"Reservar una cita","description":"La hora se vuelve a comprobar contra el motor antes de guardar, y el solape lo impide Postgres con una constraint de exclusión: si dos sistemas piden el mismo hueco a la vez, el segundo recibe 409 `slot_taken`. El cliente se indica con `client_id` o se describe en `client` (se reconoce por teléfono o correo antes de crear ficha).\n\nPermisos: `appointments:write`.","tags":["Citas"],"x-required-scopes":["appointments:write"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAppointment"}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentEnvelope"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado: el hueco acaba de ocuparse (`slot_taken`), la cita ya estaba anulada, etc. El código concreto va en `error.details.code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/appointments/{id}":{"get":{"operationId":"getAppointment","summary":"Una cita","description":"Permisos: `appointments:read`.","tags":["Citas"],"x-required-scopes":["appointments:read"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentEnvelope"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe en este centro (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/appointments/{id}/cancel":{"post":{"operationId":"cancelAppointment","summary":"Anular una cita","description":"El motivo queda en la ficha y viaja en el evento `appointment.cancelled`.\n\nPermisos: `appointments:write`.","tags":["Citas"],"x-required-scopes":["appointments:write"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelAppointment"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentEnvelope"}}}},"400":{"description":"Petición mal formada (`validation_failed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Clave ausente, revocada o caducada (`unauthenticated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La clave no tiene los permisos que pide el endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe en este centro (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflicto de estado: el hueco acaba de ocuparse (`slot_taken`), la cita ya estaba anulada, etc. El código concreto va en `error.details.code`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Demasiadas peticiones con esta clave (`rate_limited`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"tags":[{"name":"Cuenta","description":"La clave y el centro al que pertenece."},{"name":"Catálogo","description":"Locales, servicios y profesionales."},{"name":"Disponibilidad","description":"Qué huecos hay libres."},{"name":"Clientes","description":"Fichas de las clientas del centro."},{"name":"Citas","description":"Consultar, reservar y anular."}],"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"bb_…","description":"Clave de API del centro (Ajustes → API)."}},"schemas":{"Me":{"type":"object","properties":{"tenant":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"slug":{"type":"string","minLength":3,"maxLength":63,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$"},"name":{"type":"string"}},"required":["id","slug","name"]},"key":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["catalog:read","availability:read","clients:read","clients:write","appointments:read","appointments:write"]}},"expires_at":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"null"}]}},"required":["id","name","prefix","scopes","expires_at"]}},"required":["tenant","key"]},"Location":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"timezone":{"type":"string"},"address_line":{"anyOf":[{"type":"string"},{"type":"null"}]},"postal_code":{"anyOf":[{"type":"string"},{"type":"null"}]},"city":{"anyOf":[{"type":"string"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","timezone","address_line","postal_code","city","phone","email"]},"LocationList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}},"required":["items"]},"Service":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"group_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"group_name":{"type":"string"},"location_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"duration_minutes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"price_cents":{"type":"integer","minimum":0,"maximum":9007199254740991},"is_collective":{"type":"boolean"},"max_capacity":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"bookable_online":{"type":"boolean"}},"required":["id","name","group_id","group_name","location_id","duration_minutes","price_cents","is_collective","max_capacity","bookable_online"]},"ServiceList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}},"required":["items"]},"Professional":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"is_active":{"type":"boolean"}},"required":["id","name","is_active"]},"ProfessionalList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Professional"}}},"required":["items"]},"Slot":{"type":"object","properties":{"starts_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"ends_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"local_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Fecha civil del centro, `YYYY-MM-DD`"},"local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"professional_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"professional_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"room_id":{"anyOf":[{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},{"type":"null"}]},"room_name":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["starts_at","ends_at","local_date","local_time","professional_id","professional_name","room_id","room_name"]},"Availability":{"type":"object","properties":{"service":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"duration_minutes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"price_cents":{"type":"integer","minimum":0,"maximum":9007199254740991},"location_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"timezone":{"type":"string"}},"required":["id","name","duration_minutes","price_cents","location_id","timezone"]},"days":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Fecha civil del centro, `YYYY-MM-DD`"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/Slot"}}},"required":["date","slots"]}},"total_slots":{"type":"integer","minimum":0,"maximum":9007199254740991},"empty_reason":{"type":"string","enum":["none","no_professionals","no_rooms","no_equipment","no_working_hours","all_busy"]}},"required":["service","days","total_slots","empty_reason"]},"Client":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"first_name":{"type":"string"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"null"}]},"phone":{"anyOf":[{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["id","first_name","last_name","email","phone","created_at"]},"ClientList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Client"}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"page":{"type":"integer","minimum":1,"maximum":9007199254740991},"page_size":{"type":"integer","minimum":1,"maximum":9007199254740991}},"required":["items","total","page","page_size"]},"ClientInput":{"type":"object","properties":{"first_name":{"type":"string","minLength":2,"maxLength":80},"last_name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}]},"email":{"anyOf":[{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","const":""}]},{"type":"null"}]},"phone":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}]}},"required":["first_name"]},"ClientEnvelope":{"type":"object","properties":{"client":{"$ref":"#/components/schemas/Client"}},"required":["client"]},"Appointment":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"status":{"type":"string","enum":["pending","confirmed","in_progress","completed","no_show","cancelled"]},"source":{"type":"string","enum":["staff","online","api","assistant"]},"starts_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"ends_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"local_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Fecha civil del centro, `YYYY-MM-DD`"},"local_time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"timezone":{"type":"string"},"location_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"client":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"phone":{"anyOf":[{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},{"type":"null"}]}},"required":["id","name","phone"]},"service":{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"},"duration_minutes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"price_cents":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["id","name","duration_minutes","price_cents"]},"professional":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"room":{"anyOf":[{"type":"object","properties":{"id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"notes":{"anyOf":[{"type":"string"},{"type":"null"}]},"cancel_reason":{"anyOf":[{"type":"string"},{"type":"null"}]},"created_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"}},"required":["id","status","source","starts_at","ends_at","local_date","local_time","timezone","location_id","client","service","professional","room","notes","cancel_reason","created_at"]},"AppointmentList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}},"total":{"type":"integer","minimum":0,"maximum":9007199254740991},"page":{"type":"integer","minimum":1,"maximum":9007199254740991},"page_size":{"type":"integer","minimum":1,"maximum":9007199254740991}},"required":["items","total","page","page_size"]},"AppointmentEnvelope":{"type":"object","properties":{"appointment":{"$ref":"#/components/schemas/Appointment"}},"required":["appointment"]},"CreateAppointment":{"type":"object","properties":{"service_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"starts_at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"professional_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"client_id":{"type":"string","format":"uuid","pattern":"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"},"client":{"$ref":"#/components/schemas/ClientInput"},"status":{"default":"confirmed","type":"string","enum":["pending","confirmed"]},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]}},"required":["service_id","starts_at"]},"CancelAppointment":{"type":"object","properties":{"reason":{"type":"string","minLength":3,"maxLength":500}},"required":["reason"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]}},"required":["error"]}}},"security":[{"ApiKey":[]}],"x-scopes":["catalog:read","availability:read","clients:read","clients:write","appointments:read","appointments:write"]}