DentiSoft Gateway API
API Gateway centralizado para integraciones externas y el portal de pacientes.
Flujo de integración
API Key Auth
Rate limiting por plan: 200 / 1 000 / 5 000 req por 15 min.
Aislamiento de datos
Cada clínica solo accede a sus propios datos. Nunca se mezcla información entre organizaciones.
Alta disponibilidad
Infraestructura con tolerancia a fallos y recuperación automática para garantizar continuidad del servicio.
Autenticación
Requerida en todos los endpoints protegidos. Se obtiene desde el panel de administración de DentiSoft.
X-API-Key: ds_live_tu_clave_aqui
JWT de sesión (30 días) retornado tras set-password o login. Requerido en todos los endpoints /patient/* protegidos.
Flujo de registro (primera vez)
POST /patient/auth/register — cédula + celular + clinicSlug → envía OTP
POST /patient/auth/verify-otp — cédula + clinicSlug + code → verificationToken
POST /patient/auth/set-password — verificationToken + password → token
Login (cuentas ya registradas)
POST /patient/auth/login — cédula o correo + contraseña⚠️ No requiere clinicSlug
Rate Limits
| Plan | Requests / 15 min | Uso típico |
|---|---|---|
| free | 200 | Pruebas, desarrollo |
| pro | 1 000 | Integraciones de producción |
| enterprise | 5 000 | Grandes volúmenes, cadenas de clínicas |
Cuando se excede el límite se retorna 429 Too Many Requests con el header Retry-After.
Health
/health
Verifica el estado operativo del servicio.
Respuesta 200 — Servicio operativo
{ "status": "ok", "ts": "2026-04-02T18:00:00.000Z", "requestId": "req_abc123" }
Respuesta 503 — Mantenimiento
{ "status": "unavailable", "message": "Service temporarily unavailable" }
/health/ready
Readiness probe para monitoreo de infraestructura.
{ "status": "ready" }
Patient Portal
Endpoints para la app móvil de pacientes. Los endpoints de auth (/patient/auth/*) no requieren X-API-Key.
/patient/auth/register
Registra al paciente y envía un OTP por SMS (o correo si no hay celular). Único paso que pide nombre de clínica.
Body (JSON)
{ "cedula": "1234567890", "phone": "+593987654321", "clinicSlug": "mi-clinica" }
Parámetros
| cedula | string | Requerido | Cédula del paciente |
| phone | string | Opcional | Celular. Si se omite, OTP va por correo |
| clinicSlug | string | Requerido | Identificador de la clínica |
Respuesta 200 — OTP enviado
{ "success": true, "otpSent": true, "channel": "sms", "maskedTarget": "593****21" }
Respuesta 422 — Sin contacto
{ "success": false, "error": "Sin telefono ni correo para el codigo" }
/patient/auth/verify-otp
Verifica el OTP y retorna un verificationToken de 15 min para establecer contraseña.
Body (JSON)
{ "cedula": "1234567890", "clinicSlug": "mi-clinica", "code": "123456" }
Respuesta 200 — Token de verificación
{ "success": true, "verificationToken": "eyJhbGciOiJIUzI1NiIs...", "needsPassword": true }
Respuesta 422 — Código inválido
{ "success": false, "error": "Codigo invalido o expirado" }
/patient/auth/set-password
Establece la contraseña (primera vez). Retorna el token de sesión directamente.
Body (JSON)
{ "verificationToken": "eyJhbGci...", "password": "MiContrasena123" }
Parámetros
| verificationToken | JWT | Requerido | Obtenido en verify-otp (expira en 15 min) |
| password | string | Requerido | Mínimo 6 caracteres |
Respuesta 200 — Sesión iniciada
{ "success": true, "token": "eyJhbGciOiJIUzI1NiIs...", "patient": { "id": "uuid", "cedula": "1234567890", … } }
Respuesta 401 — Token inválido/expirado
{ "success": false, "error": "Token de verificacion invalido" }
/patient/auth/login
Login para pacientes con cuenta ya creada. Solo cédula (o correo) + contraseña — no se pide clinicSlug.
Body (JSON)
{ "identifier": "1234567890", "password": "MiContrasena123" }
Parámetros
| identifier | string | Requerido | Cédula o correo electrónico |
| password | string | Requerido | Contraseña del paciente |
Respuesta 200 — Sesión iniciada
{ "success": true, "token": "eyJhbGciOiJIUzI1NiIs...", "patient": { "id": "uuid", "cedula": "1234567890", … } }
Respuesta 401 — Credenciales incorrectas
{ "success": false, "error": "Credenciales incorrectas" }
/patient/me
Retorna el perfil del paciente autenticado.
Respuesta 200
{ "id": "uuid", "firstName": "Ana", "lastName": "García", "email": "ana@example.com", "phone": "+521234567890", "dateOfBirth": "1990-05-15", "address": "Av. Principal 123" }
/patient/me
Actualiza campos mutables del perfil (teléfono, domicilio).
Body (JSON)
{ "phone": "+521234567890", "address": "Nueva dirección 456" }
Respuesta 200
{ "success": true, "patient": { "phone": "+521234567890", … } }
/patient/appointments
Lista las citas del paciente autenticado.
Query Params
| status | string | upcoming | past | all |
| limit | number | Máx por página (default 20) |
Respuesta 200
{ "appointments": [{ "id": "uuid", "date": "2026-04-10", "time": "10:00", "dentist": "Dr. Martínez", "status": "scheduled" }] }
/patient/appointments
Agenda una nueva cita para el paciente autenticado.
Body (JSON)
{ "dentistId": "uuid-del-dentista", "date": "2026-04-10", "time": "10:00", "reason": "Limpieza dental" }
Respuesta 201
{ "success": true, "appointment": { "id": "uuid", "status": "scheduled" } }
/patient/treatments
Lista todos los tratamientos del paciente autenticado.
{ "treatments": [{ "id": "uuid", "name": "Extracción simple", "status": "completed", "date": "2026-03-15", "price": 800.00 }] }
/patient/invoices
Lista todas las facturas del paciente autenticado.
{ "invoices": [{ "id": "uuid", "number": "INV-0042", "date": "2026-03-15", "total": 800.00, "status": "paid" }] }
Connect API
API de integración B2B para sistemas de terceros. Todos los endpoints requieren X-API-Key. Los datos están aislados por clínica.
/connect/v1/patients
Lista paginada de pacientes de la clínica.
Query Params
| page | number | Página (default 1) |
| limit | number | Por página (max 100, default 20) |
| search | string | Filtro por nombre o email |
Respuesta 200
{ "patients": [{ … }], "pagination": { "page": 1, "limit": 20, "total": 150 } }
/connect/v1/patients/:id
Obtiene un paciente por UUID.
{ "id": "uuid", "firstName": "Ana", "lastName": "García", "email": "ana@example.com", "dateOfBirth": "1990-05-15" }
/connect/v1/patients
Crea un nuevo paciente en la clínica.
Body (JSON)
{ "firstName": "Ana", // ✅ requerido "lastName": "García", // ✅ requerido "email": "ana@ex.com", // ✅ requerido "phone": "+52123456", "dateOfBirth": "1990-05-15" }
Respuesta 201
{ "success": true, "patient": { "id": "new-uuid", "firstName": "Ana" } }
409 Conflict si el email ya existe en la clínica.
/connect/v1/appointments
Citas en un rango de fechas de la clínica.
Query Params
| from | YYYY-MM-DD | Fecha inicio |
| to | YYYY-MM-DD | Fecha fin (default hoy) |
| dentistId | UUID | Filtrar por dentista |
Respuesta 200
{ "appointments": [{ "id": "uuid", "patientName": "Ana García", "dentistName": "Dr. Martínez", "date": "2026-04-10", "status": "scheduled" }] }
/connect/v1/appointments
Crea una nueva cita desde un sistema externo.
Body (JSON)
{ "patientId": "uuid-paciente", // ✅ "dentistId": "uuid-dentista", // ✅ "date": "2026-04-10", // ✅ "time": "10:00", // ✅ "reason": "Revisión general" // ✅ }
Respuesta 201
{ "success": true, "appointment": { "id": "new-uuid", "status": "scheduled" } }
/connect/v1/webhooks
Lista los webhooks registrados para la API Key.
{ "webhooks": [{ "id": "uuid", "url": "https://myapp.com/hooks/dentisoft", "events": ["appointment.created", "invoice.paid"], "isActive": true }] }
/connect/v1/webhooks
Registra un nuevo endpoint para recibir eventos.
Body (JSON)
{ "url": "https://myapp.com/hooks/ds", "events": [ "appointment.created", "appointment.updated", "appointment.cancelled", "patient.created", "invoice.paid" ] }
Respuesta 201
{ "success": true, "webhook": { "id": "uuid", "url": "https://myapp.com/hooks/ds", "secret": "whsec_abc123xyz" } }
Guarda secret — solo se muestra una vez. Se usa para verificar firmas HMAC.
Connect API — Invoices
/connect/v1/invoices
Auth: X-API-Key
Returns all invoices for the clinic. Optional query params: status (pending | paid | partial | overdue | cancelled), patientId.
GET /connect/v1/invoices?status=pending X-API-Key: sk_live_...
/connect/v1/invoices/overdue
Auth: X-API-Key
Returns all unpaid invoices whose due date has passed.
/connect/v1/invoices/:id
Auth: X-API-Key
Returns a single invoice with its line items.
/connect/v1/invoices/:id/status
Auth: X-API-Key
Updates the payment status of an invoice. When marking as paid, include paidAmount.
PATCH /connect/v1/invoices/:id/status
Content-Type: application/json
X-API-Key: sk_live_...
{
"status": "paid",
"paidAmount": 350.00
}
Connect API — Treatments
/connect/v1/treatments
Auth: X-API-Key
Returns treatments for the clinic. Filter by patientId or status (pending | in_progress | completed | cancelled).
/connect/v1/treatments/:id
Auth: X-API-Key
Returns a single treatment record.
/connect/v1/treatments/:id/status
Auth: X-API-Key
Updates the status of a treatment.
PATCH /connect/v1/treatments/:id/status
Content-Type: application/json
X-API-Key: sk_live_...
{
"status": "completed"
}
Connect API — Inventory
/connect/v1/inventory
Auth: X-API-Key
Returns all inventory items. Optional query params: search (name), category.
/connect/v1/inventory/low-stock
Auth: X-API-Key
Returns items whose current quantity is at or below their minimum stock threshold.
/connect/v1/inventory/:id/movement
Auth: X-API-Key
Records a stock movement for an item. type: in (stock received) or out (stock consumed).
POST /connect/v1/inventory/:id/movement
Content-Type: application/json
X-API-Key: sk_live_...
{
"type": "out",
"quantity": 5,
"reason": "Used in procedure"
}
Connect API — Dentists
/connect/v1/dentists
Auth: X-API-Key
Returns all active dentists (users with appointment access) for the clinic.
/connect/v1/dentists/:id/availability?date=YYYY-MM-DD
Auth: X-API-Key
Returns the booked appointment slots for a dentist on a given date. Use this to show availability before booking.
GET /connect/v1/dentists/:id/availability?date=2025-08-15
X-API-Key: sk_live_...
// Response
{
"success": true,
"data": {
"dentistId": "uuid",
"date": "2025-08-15",
"bookedSlots": [
{ "appointmentId": "uuid", "time": "09:00", "durationMinutes": 30, "status": "Confirmada" }
]
}
}
Connect API — Stats
/connect/v1/stats/overview
Auth: X-API-Key
Returns a summary of clinic metrics: total patients, appointment counts, and revenue.
GET /connect/v1/stats/overview
X-API-Key: sk_live_...
// Response
{
"success": true,
"data": {
"patients": { "total": 120, "active": 95 },
"appointments": { "today": 8, "this_month": 52, "pending": 14 },
"revenue": { "revenue_this_month": 12500.00, "revenue_all_time": 98400.00 }
}
}
Códigos de Error
| Código | Significado | Causa común |
|---|---|---|
| 200 | OK | Solicitud exitosa |
| 201 | Created | Recurso creado exitosamente |
| 400 | Bad Request | Body inválido o parámetros faltantes |
| 401 | Unauthorized | OTP inválido, token expirado o ausente |
| 403 | Forbidden | API Key sin permisos para este recurso |
| 404 | Not Found | Recurso no existe o no pertenece a tu clínica |
| 409 | Conflict | Email duplicado u otro conflicto de unicidad |
| 429 | Too Many Requests | Rate limit del plan excedido |
| 500 | Internal Error | Error inesperado — reportar con requestId |
| 503 | Service Unavailable | Circuit breaker abierto (Core no responde) |
Formato de error estándar
{ "success": false, "error": "Mensaje de error legible", "requestId": "req_abc123xyz" // incluir en reportes de soporte }
Webhook Events
Los webhooks se entregan como POST a tu URL con el header X-DentiSoft-Signature (HMAC-SHA256 del body usando tu secret).
| Evento | Disparado cuando… |
|---|---|
| appointment.created | Se agenda una nueva cita |
| appointment.updated | Se modifica fecha, hora o dentista |
| appointment.cancelled | La cita es cancelada |
| patient.created | Se registra un paciente nuevo |
| invoice.paid | Una factura es marcada como pagada |
Ejemplo de payload — appointment.created
{ "event": "appointment.created", "ts": "2026-04-10T10:00:00.000Z", "requestId": "req_xyz789", "data": { "id": "uuid-cita", "patientId": "uuid-paciente", "patientName": "Ana García", "dentistId": "uuid-dentista", "dentistName": "Dr. Martínez", "date": "2026-04-10", "time": "10:00", "reason": "Limpieza dental", "status": "scheduled" } }