{"openapi":"3.0.3","info":{"title":"Zeit API pública","version":"1.0.0","description":"Integra Zeit con tu ERP u otro sistema. Autenticación con API key (`Authorization: Bearer zk_live_...`), creada desde Ajustes → Integraciones. Cada key solo ve los datos de su negocio y solo lo que sus scopes permiten. Los cambios en Zeit se notifican por webhooks firmados (ver la guía de integración)."},"servers":[{"url":"/api/v1","description":"Base de la API"}],"tags":[{"name":"Citas"},{"name":"Clientes"},{"name":"Staff y servicios"},{"name":"Conversaciones"},{"name":"Pagos"},{"name":"Estadísticas"}],"components":{"securitySchemes":{"ApiKey":{"type":"http","scheme":"bearer","description":"API key `zk_live_...`. Los scopes disponibles están en cada operación (`x-required-scope`)."}},"schemas":{"Error":{"type":"object","properties":{"statusCode":{"type":"integer"},"message":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"error":{"type":"string"}}},"Appointment":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","CONFIRMED","CANCELLED","COMPLETED","NO_SHOW","RESCHEDULED"]},"scheduledAt":{"type":"string","format":"date-time"},"durationMinutes":{"type":"integer"},"totalPrice":{"type":"number"},"notes":{"type":"string","nullable":true},"branchId":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"client":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"email":{"type":"string","nullable":true}}},"staff":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"service":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"durationMinutes":{"type":"integer"},"price":{"type":"number"}}},"payment":{"type":"object","nullable":true,"properties":{"id":{"type":"string"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"method":{"$ref":"#/components/schemas/PaymentMethod"},"amount":{"type":"number"}}}}},"PaymentStatus":{"type":"string","enum":["PENDING","AWAITING_PROOF","PROOF_SUBMITTED","VERIFIED","PAID","REFUNDED","FAILED"]},"PaymentMethod":{"type":"string","enum":["CASH","BANK_TRANSFER","YAPPY","STRIPE","OTHER"]},"Client":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"channel":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"visitCount":{"type":"integer"},"lifetimeValue":{"type":"number"},"lastVisitAt":{"type":"string","format":"date-time","nullable":true},"noShowCount":{"type":"integer"},"cancellationCount":{"type":"integer"},"createdAt":{"type":"string","format":"date-time"}}},"ClientProfile":{"allOf":[{"$ref":"#/components/schemas/Client"},{"type":"object","properties":{"appointments":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"},"description":"Últimas 50 citas."}}}]},"Payment":{"type":"object","properties":{"id":{"type":"string"},"amount":{"type":"number"},"method":{"$ref":"#/components/schemas/PaymentMethod"},"status":{"$ref":"#/components/schemas/PaymentStatus"},"proofSubmittedAt":{"type":"string","format":"date-time","nullable":true},"verifiedAt":{"type":"string","format":"date-time","nullable":true},"appointmentId":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"Service":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"durationMinutes":{"type":"integer"},"price":{"type":"number"},"depositRequired":{"type":"number","nullable":true},"categoryId":{"type":"string","nullable":true}}},"Staff":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"specialty":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"avatarUrl":{"type":"string","nullable":true},"workingHours":{"$ref":"#/components/schemas/WeeklyHours"},"breakTimes":{"$ref":"#/components/schemas/WeeklyHours"},"serviceIds":{"type":"array","items":{"type":"string"}}}},"WeeklyHours":{"type":"object","description":"Por día (`mon`..`sun`), bloques en hora local del negocio.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","example":"09:00"},"end":{"type":"string","example":"18:00"}}}}},"Slot":{"type":"object","properties":{"staffId":{"type":"string"},"staffName":{"type":"string"},"start":{"type":"string","format":"date-time","description":"Instante UTC: envíalo tal cual como `scheduledAt` al crear la cita."},"local":{"type":"string","example":"10:30","description":"Hora de pared del negocio."}}},"Message":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"author":{"type":"string","enum":["CUSTOMER","BOT","HUMAN"]},"body":{"type":"string"},"status":{"type":"string","enum":["PENDING","SENDING","SENT","FAILED"]},"errorReason":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"}}},"Thread":{"type":"object","properties":{"clientId":{"type":"string"},"name":{"type":"string","nullable":true},"channel":{"type":"string"},"channelUserId":{"type":"string","nullable":true},"lastMessageAt":{"type":"string","format":"date-time"},"preview":{"type":"string"},"lastDirection":{"type":"string","nullable":true},"humanTakeover":{"type":"boolean"},"unread":{"type":"boolean"}}}}},"security":[{"ApiKey":[]}],"paths":{"/public/availability":{"get":{"summary":"Horarios libres de un día","description":"Mismos horarios que ofrece el bot de WhatsApp: horario laboral del staff menos citas, descansos y bloqueos. Sin `staffId` consulta a todo el staff que ofrece el servicio. Máximo 120 días hacia adelante.","tags":["Citas"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","parameters":[{"name":"serviceId","in":"query","required":true,"schema":{"type":"string"},"description":"Servicio a agendar."},{"name":"date","in":"query","required":true,"schema":{"type":"string","format":"date","example":"2026-10-05"},"description":"Día en la zona horaria del negocio."},{"name":"staffId","in":"query","required":false,"schema":{"type":"string"},"description":"Limita a un miembro del staff."},{"name":"branchId","in":"query","required":false,"schema":{"type":"string"},"description":"Limita a una sucursal."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string"},"timezone":{"type":"string"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/Slot"}}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments":{"get":{"summary":"Listar citas","tags":["Citas"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","parameters":[{"name":"from","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Desde (inclusive)."},{"name":"to","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"Hasta (exclusivo)."},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["PENDING","CONFIRMED","CANCELLED","COMPLETED","NO_SHOW","RESCHEDULED"]},"description":"Estado."},{"name":"staffId","in":"query","required":false,"schema":{"type":"string"},"description":""},{"name":"clientId","in":"query","required":false,"schema":{"type":"string"},"description":""},{"name":"branchId","in":"query","required":false,"schema":{"type":"string"},"description":""},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Elementos por página."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` de la página anterior."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}},"nextCursor":{"type":"string","nullable":true}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Crear una cita","description":"Valida que cliente, staff y servicio sean del negocio y que el staff esté libre (409 si el horario se ocupó). Nace `PENDING`; pasa a `CONFIRMED` al verificarse el pago. Usa un `start` devuelto por `/public/availability`.","tags":["Citas"],"security":[{"ApiKey":["appointments:write"]}],"x-required-scope":"appointments:write","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Clave única por operación (1-128 caracteres ASCII imprimibles, sin espacios). Un reintento con la misma clave y el mismo cuerpo devuelve la respuesta original sin volver a ejecutar (con el header `Idempotent-Replayed: true`); con otro cuerpo devuelve 422. Vigente 24 h."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["clientId","staffId","serviceId","scheduledAt"],"properties":{"clientId":{"type":"string"},"staffId":{"type":"string"},"serviceId":{"type":"string"},"scheduledAt":{"type":"string","format":"date-time"},"notes":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"El staff ya tiene una cita en ese horario, o hay otra petición con la misma Idempotency-Key en curso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Idempotency-Key reutilizada con un cuerpo distinto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments/{id}":{"get":{"summary":"Detalle de una cita","tags":["Citas"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la cita"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe en este negocio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments/{id}/cancel":{"post":{"summary":"Cancelar una cita","description":"Idempotente. 409 si la cita ya está `COMPLETED` o `NO_SHOW`.","tags":["Citas"],"security":[{"ApiKey":["appointments:write"]}],"x-required-scope":"appointments:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la cita"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"La cita ya se realizó.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments/{id}/reschedule":{"post":{"summary":"Reagendar una cita","description":"Revalida la disponibilidad del staff. 409 si la cita está cancelada o completada, o el nuevo horario está ocupado.","tags":["Citas"],"security":[{"ApiKey":["appointments:write"]}],"x-required-scope":"appointments:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la cita"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduledAt"],"properties":{"scheduledAt":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Estado inválido u horario ocupado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments/{id}/transfer":{"post":{"summary":"Transferir una cita a otra sucursal","description":"Idempotente. Solo citas `PENDING` o `CONFIRMED`. El mismo staff y servicio deben estar habilitados en la sucursal de destino (409 si no). Consulta `/public/branches` para los ids.","tags":["Citas"],"security":[{"ApiKey":["appointments:write"]}],"x-required-scope":"appointments:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la cita"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["branchId"],"properties":{"branchId":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cita o sucursal inexistente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Estado inválido, sucursal inactiva, o el staff/servicio no opera allí.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/appointments/{id}/complete":{"post":{"summary":"Marcar una cita como completada","description":"Idempotente; suma una visita al cliente una sola vez.","tags":["Citas"],"security":[{"ApiKey":["appointments:write"]}],"x-required-scope":"appointments:write","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id de la cita"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"La cita está cancelada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/clients":{"get":{"summary":"Listar / buscar clientes","tags":["Clientes"],"security":[{"ApiKey":["clients:read"]}],"x-required-scope":"clients:read","parameters":[{"name":"search","in":"query","required":false,"schema":{"type":"string","maxLength":80},"description":"Nombre o teléfono (contiene)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Elementos por página."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"`nextCursor` de la página anterior."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Client"}},"nextCursor":{"type":"string","nullable":true}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Crear un cliente","tags":["Clientes"],"security":[{"ApiKey":["clients:write"]}],"x-required-scope":"clients:write","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Clave única por operación (1-128 caracteres ASCII imprimibles, sin espacios). Un reintento con la misma clave y el mismo cuerpo devuelve la respuesta original sin volver a ejecutar (con el header `Idempotent-Replayed: true`); con otro cuerpo devuelve 422. Vigente 24 h."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","example":"+50761234567"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"notes":{"type":"string"},"birthday":{"type":"string","format":"date","example":"1990-05-17","description":"Opcional. Se usa solo para el saludo de cumpleaños de clientes que aceptaron promociones."}}}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Client"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Ya existe un cliente con ese teléfono.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/clients/{id}":{"get":{"summary":"Perfil del cliente con su historial de citas","tags":["Clientes"],"security":[{"ApiKey":["clients:read"]}],"x-required-scope":"clients:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id del cliente"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientProfile"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe en este negocio.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/branches":{"get":{"summary":"Sucursales del negocio","tags":["Staff y servicios"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"isActive":{"type":"boolean"}}}}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/services":{"get":{"summary":"Servicios activos del negocio","tags":["Staff y servicios"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/staff":{"get":{"summary":"Staff activo, con los servicios que ofrece","tags":["Staff y servicios"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Staff"}}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/staff/{id}/schedule":{"get":{"summary":"Horario semanal de un miembro del staff","tags":["Staff y servicios"],"security":[{"ApiKey":["appointments:read"]}],"x-required-scope":"appointments:read","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id del staff"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"workingHours":{"$ref":"#/components/schemas/WeeklyHours"},"breakTimes":{"$ref":"#/components/schemas/WeeklyHours"}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No existe o está inactivo.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/conversations":{"get":{"summary":"Listar conversaciones (más recientes primero)","description":"Requiere un plan con bot IA.","tags":["Conversaciones"],"security":[{"ApiKey":["inbox:reply"]}],"x-required-scope":"inbox:reply","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"`nextCursor` de la página anterior."},{"name":"branchId","in":"query","required":false,"schema":{"type":"string"},"description":"`main` = número principal."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"threads":{"type":"array","items":{"$ref":"#/components/schemas/Thread"}},"nextCursor":{"type":"string","nullable":true}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/conversations/{clientId}/messages":{"get":{"summary":"Historial de mensajes de un cliente","description":"Orden cronológico, 50 por página. No marca el hilo como leído.","tags":["Conversaciones"],"security":[{"ApiKey":["inbox:reply"]}],"x-required-scope":"inbox:reply","parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"},"description":"Id del cliente"},{"name":"cursor","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"`nextCursor` para mensajes más antiguos."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/Message"}},"nextCursor":{"type":"string","nullable":true}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cliente inexistente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Enviar un mensaje manual (handoff humano)","description":"Responde con estado `PENDING`: el envío es asíncrono y puede terminar `FAILED` (p. ej. fuera de la ventana de 24 h de WhatsApp). Consulta el historial para el estado final y `errorReason`. Activa el takeover: el bot deja de responder a ese cliente hasta `PUT .../takeover {on:false}`.","tags":["Conversaciones"],"security":[{"ApiKey":["inbox:reply"]}],"x-required-scope":"inbox:reply","parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"},"description":"Id del cliente"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["body"],"properties":{"body":{"type":"string","minLength":1,"maxLength":4000}}}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"description":"Cliente en opt-out o sin identificador de canal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Cliente inexistente.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/conversations/{clientId}/takeover":{"put":{"summary":"Tomar o devolver el control de la conversación","tags":["Conversaciones"],"security":[{"ApiKey":["inbox:reply"]}],"x-required-scope":"inbox:reply","parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"},"description":"Id del cliente"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["on"],"properties":{"on":{"type":"boolean","description":"`true` = el bot calla; `false` = se lo devuelve al bot."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"humanTakeover":{"type":"boolean"}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/conversations/{clientId}/reset":{"post":{"summary":"Resetear la conversación del bot a IDLE","description":"Borra el contexto (p. ej. un cliente atascado a mitad de una reserva). No toca el takeover ni el historial. Idempotente.","tags":["Conversaciones"],"security":[{"ApiKey":["inbox:reply"]}],"x-required-scope":"inbox:reply","parameters":[{"name":"clientId","in":"path","required":true,"schema":{"type":"string"},"description":"Id del cliente"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"state":{"type":"string","example":"IDLE"}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/payments":{"post":{"summary":"Registrar el pago de una cita","description":"Queda `PENDING` hasta `POST /public/payments/{id}/verify`. 409 si la cita ya tiene un pago verificado o está cancelada.","tags":["Pagos"],"security":[{"ApiKey":["payments:manage"]}],"x-required-scope":"payments:manage","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Clave única por operación (1-128 caracteres ASCII imprimibles, sin espacios). Un reintento con la misma clave y el mismo cuerpo devuelve la respuesta original sin volver a ejecutar (con el header `Idempotent-Replayed: true`); con otro cuerpo devuelve 422. Vigente 24 h."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["appointmentId","amount","method"],"properties":{"appointmentId":{"type":"string"},"amount":{"type":"number","exclusiveMinimum":true,"minimum":0},"method":{"$ref":"#/components/schemas/PaymentMethod"},"notes":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Creado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Pago ya verificado o cita cancelada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Idempotency-Key reutilizada con un cuerpo distinto.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/payments/{id}/verify":{"post":{"summary":"Verificar un pago","description":"Confirma la cita, suma al valor del cliente y notifica por WhatsApp. Idempotente. 409 si la cita está cancelada.","tags":["Pagos"],"security":[{"ApiKey":["payments:manage"]}],"x-required-scope":"payments:manage","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Id del pago"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Payment"}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"La cita está cancelada.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/payments/pending":{"get":{"summary":"Pagos con comprobante pendientes de verificar","tags":["Pagos"],"security":[{"ApiKey":["payments:manage"]}],"x-required-scope":"payments:manage","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/payments/summary":{"get":{"summary":"Resumen de pagos del mes","tags":["Pagos"],"security":[{"ApiKey":["payments:manage"]}],"x-required-scope":"payments:manage","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"number"},"paid":{"type":"number"},"pending":{"type":"number"},"pendingCount":{"type":"integer"}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/public/stats":{"get":{"summary":"Métricas de un período","description":"`confirmationRate` = citas CONFIRMED o COMPLETED sobre el total del período (`null` si no hay citas). `revenue` suma pagos verificados de citas del período. Rango máximo: 366 días.","tags":["Estadísticas"],"security":[{"ApiKey":["reports:view"]}],"x-required-scope":"reports:view","parameters":[{"name":"from","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Inicio (inclusive)."},{"name":"to","in":"query","required":true,"schema":{"type":"string","format":"date-time"},"description":"Fin (exclusivo)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"period":{"type":"object"},"appointments":{"type":"object","properties":{"total":{"type":"integer"},"byStatus":{"type":"object","additionalProperties":{"type":"integer"}}}},"confirmationRate":{"type":"number","nullable":true},"clients":{"type":"object","properties":{"new":{"type":"integer"},"withAppointments":{"type":"integer"}}},"revenue":{"type":"number"}}}}}},"401":{"description":"Falta la API key, es inválida, fue revocada o venció.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"La key no tiene el scope requerido, el tenant no está activo o su plan no incluye la API pública.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Límite de peticiones por minuto excedido. Ver `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}