Desarrolladores

La API de CarelyCloud.

La misma API que usa nuestra aplicación, con una clave en vez de un inicio de sesión. Citas, pacientes, disponibilidad, y webhooks para todo lo que pasa en la clínica. Incluida en los planes Grupo y Enterprise; la prueba gratis puede probarla.

Autenticación

Un administrador crea las claves en Ajustes → Integraciones. La clave se muestra una sola vez. Mándala en cada petición en la cabecera X-API-Key.

curl https://carelycloud-production.up.railway.app/clinics/me \
  -H "X-API-Key: cc_live_xxxxxxxxxxxxxxxxxxxx"

La clave actúa en nombre de la clínica, no de una persona, y solo alcanza una parte de la API:

  • Lectura: citas, pacientes (con notas y archivos), profesionales, disponibilidad, servicios, el perfil de la clínica y las métricas del inicio.
  • Escritura: citas y pacientes, incluidas sus notas y archivos.
  • Todo lo demás (equipo, ajustes, facturación, integraciones, notas con IA) responde 403 API_KEY_SCOPE.

El acceso se comprueba en cada petición contra el plan de la clínica. Cuando una prueba termina o una suscripción se suspende, todas las claves responden 402 de inmediato, sin revocar nada a mano. Revocar una clave en Ajustes aplica en la siguiente petición.

Convenciones

  • URL base: https://carelycloud-production.up.railway.app
  • JSON de entrada y de salida. Manda Content-Type: application/json en las escrituras.
  • Las fechas son ISO 8601 con zona horaria. Guardamos en UTC; manda el desfase que quieras y lo convertimos. Los huecos vuelven en la zona horaria de la clínica.
  • Las listas paginadas aceptan page (desde 1) y limit. GET /appointments sin page devuelve todo el rango.
  • Los errores son JSON con un código estable, un mensaje y detalles opcionales:
{ "code": "SLOT_TAKEN", "message": "SLOT_TAKEN", "details": null }

Citas

GET/appointmentsLista citas. Filtros: start, end (ISO 8601), doctor_id, patient_id, location_id, status, q, page, limit.
POST/appointmentsCrea una cita. Valida horario y traslapes; la sede (location_id) la fija el servidor según el horario del profesional.
GET/appointments/{id}Una cita.
PATCH/appointments/{id}Reprograma o cambia el estado (ver transiciones).
DELETE/appointments/{id}Cancela. No se puede cancelar una cita completada ni una no asistida.
GET/appointments/{id}/auditHistorial de cambios de una cita.

Filtra por rango (start, end), profesional, paciente, estado o texto libre (q). Sin rango devuelve todo, así que manda uno.

curl "https://carelycloud-production.up.railway.app/appointments?start=2026-09-15T00:00:00Z&end=2026-09-15T23:59:59Z&doctor_id=3" \
  -H "X-API-Key: cc_live_..."

Crear una cita pasa por las mismas reglas que la aplicación. El profesional es obligatorio, y el hueco tiene que estar dentro de su horario y libre:

curl -X POST https://carelycloud-production.up.railway.app/appointments \
  -H "X-API-Key: cc_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "doctor_id": 3,
    "service_id": 1,
    "patient_id": 42,
    "start_at": "2026-09-15T15:00:00Z",
    "end_at":   "2026-09-15T15:30:00Z",
    "status":   "confirmed",
    "channel":  "manual",
    "notes":    "Booked from our CRM"
  }'

Qué puede responder crear o reagendar, y por qué

  • DOCTOR_REQUIRED — no se mandó doctor_id.
  • DOCTOR_HAS_NO_SCHEDULE — el profesional todavía no tiene horario configurado.
  • OUTSIDE_WORKING_HOURS — la hora cae fuera de su horario, en un descanso o en un día bloqueado.
  • SLOT_TAKEN (409) — otra cita activa se cruza.

Para no adivinar, pide primero GET /doctors/{id}/slots para una fecha y elige uno marcado available.

Los cambios de estado van por PATCH. Transiciones permitidas: scheduled → confirmed | cancelled | no_show; confirmed → completed | cancelled | no_show. Completed, cancelled y no_show son finales. DELETE es un atajo para cancelar.

curl -X PATCH https://carelycloud-production.up.railway.app/appointments/981 \
  -H "X-API-Key: cc_live_..." -H "Content-Type: application/json" \
  -d '{ "status": "cancelled", "cancel_reason": "Patient called" }'

Pacientes

GET/patientsLista pacientes. Filtros: q, doctor_id, has_no_shows, include_inactive, page, limit.
POST/patientsCrea un paciente. 409 si hay un posible duplicado, salvo con force=true.
GET/patients/{id}Un paciente.
PATCH/patients/{id}Actualiza datos de contacto o notas.
DELETE/patients/{id}Archiva un paciente. Sus citas se conservan.
GET/patients/{id}/appointmentsCitas de un paciente, la más reciente primero.
GET/patients/{id}/notesNotas de consulta de un paciente.
POST/patients/{id}/notesAgrega una nota de consulta.
GET/patients/{id}/filesArchivos adjuntos de un paciente.
POST/patients/{id}/filesAdjunta un archivo (multipart/form-data, máx. 10 MB).
GET/patients/exportCSV de todos los pacientes.
POST/patients/importImporta pacientes desde CSV (multipart/form-data).

Crear un paciente busca posibles duplicados por teléfono o correo y responde 409 con las coincidencias; manda force=true para crearlo igual.

curl -X POST "https://carelycloud-production.up.railway.app/patients?force=false" \
  -H "X-API-Key: cc_live_..." -H "Content-Type: application/json" \
  -d '{
    "first_name": "Marta",
    "last_name":  "Ríos",
    "phone":      "+50499990000",
    "email":      "marta@example.com",
    "birth_date": "1992-04-12",
    "gender":     "female",
    "preferred_channel": "whatsapp"
  }'

La búsqueda cubre nombre, teléfono y correo. Paginación de 20 por página por defecto, 500 como máximo.

curl "https://carelycloud-production.up.railway.app/patients?q=marta&page=1&limit=20" -H "X-API-Key: cc_live_..."

Profesionales, disponibilidad y servicios

Solo lectura. Los profesionales y los servicios se administran desde la aplicación.

GET/doctorsProfesionales de la clínica.
GET/doctors/{id}/availabilityHorario semanal de un profesional.
GET/doctors/{id}/exceptionsDías libres y cambios puntuales.
GET/doctors/{id}/slots?date=YYYY-MM-DD&service_id=NEspacios reservables de un profesional para un día.
GET/doctors/{id}/servicesServicios que ofrece un profesional.
GET/servicesCatálogo de servicios con duración y precio.
GET/locationsSedes activas de la clínica, la principal primero.
GET/clinics/meTu clínica: nombre, zona horaria, plan.
GET/dashboard/statsLos números de hoy, como los muestra el panel.
curl "https://carelycloud-production.up.railway.app/doctors/3/slots?date=2026-09-15&service_id=1" -H "X-API-Key: cc_live_..."

[
  { "start_at": "2026-09-15T15:00:00-06:00", "end_at": "2026-09-15T15:30:00-06:00", "available": true },
  { "start_at": "2026-09-15T15:15:00-06:00", "end_at": "2026-09-15T15:45:00-06:00", "available": false }
]

Webhooks

Un administrador registra una URL https en Ajustes → Integraciones y elige los eventos. Hacemos POST de cada uno cuando ocurre, una vez, con 8 segundos de espera. Sin reintentos: si tu endpoint está caído, la entrega queda como fallida en el registro y puedes reconstruir desde la API.

appointment.createdse creó una cita (aplicación, página de reservas o API)
appointment.updatedcambió la hora, el profesional, el servicio o las notas
appointment.confirmedpasó a confirmada
appointment.completedpasó a completada
appointment.cancelledcancelada (por el personal, el paciente o la API)
appointment.no_showmarcada como no-show
patient.createdse creó un paciente (aplicación, página de reservas, importación o API)

Cada entrega lleva el nombre del evento y una firma HMAC-SHA256 del cuerpo, con el secreto que se mostró una vez al crear el endpoint:

POST https://your-server.example.com/carelycloud
Content-Type: application/json
X-CarelyCloud-Event: appointment.confirmed
X-CarelyCloud-Signature: sha256=3f1a...

{
  "event": "appointment.confirmed",
  "created_at": "2026-09-15T14:02:11.480Z",
  "data": {
    "id": 981, "clinic_id": 6, "doctor_id": 3, "service_id": 1, "patient_id": 42,
    "start_at": "2026-09-15T15:00:00Z", "end_at": "2026-09-15T15:30:00Z",
    "status": "confirmed", "channel": "online"
  }
}

Verifica la firma antes de confiar en el contenido. Compara con una función de tiempo constante:

# Python
import hmac, hashlib
def verify(secret: str, raw_body: bytes, header: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, header)

// Node
const crypto = require("crypto")
function verify(secret, rawBody, header) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header))
}
  • Solo URLs https en hosts públicos. No se siguen redirecciones.
  • Responde 2xx en menos de 8 segundos; haz el trabajo después.
  • El registro guarda las últimas 100 entregas por endpoint, con estado y duración.
  • El botón Probar en Ajustes manda un evento de ejemplo para revisar tu endpoint antes de salir en vivo.

Zapier, n8n y Make

No necesitan una app nativa: un disparador por webhook más una acción HTTP cubren toda la API.

  • Zapier: el disparador "Webhooks by Zapier · Catch Hook" recibe los eventos; la acción "Webhooks by Zapier · Custom Request" llama a la API con la cabecera X-API-Key.
  • n8n: el nodo Webhook recibe los eventos; el nodo HTTP Request llama a la API. Usa una credencial Header Auth para la clave.
  • Make: el módulo Custom Webhook recibe los eventos; el módulo HTTP llama a la API.

Primera automatización típica: patient.created → agregar el paciente a tu CRM; appointment.confirmed → avisar al grupo de Slack o WhatsApp de la clínica.

Errores

401INVALID_API_KEYClave ausente, desconocida o revocada.
402TRIAL_EXPIRED · SUBSCRIPTION_INACTIVE · PLAN_UPGRADE_REQUIREDEl plan de la clínica no permite integraciones ahora mismo.
403API_KEY_SCOPELa clave no alcanza esta ruta (ver Autenticación).
404APPOINTMENT_NOT_FOUND · PATIENT_NOT_FOUND · DOCTOR_NOT_FOUND · SERVICE_NOT_FOUNDEl recurso no existe en esta clínica.
409SLOT_TAKENCita que se cruza, o paciente probablemente duplicado (manda force=true).
422VALIDATION_ERROR · OUTSIDE_WORKING_HOURS · DOCTOR_HAS_NO_SCHEDULE · INVALID_TRANSITIONFalló la validación; details dice qué campo.
429RATE_LIMITEDDemasiadas peticiones; espera y reintenta.

¿Falta algo, o estás construyendo algo que necesita más que esto? Escríbenos: respondemos.