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 con expiración de 24 h obtenido en el flujo OTP. Se necesita en todos los endpoints /patient/ protegidos.
Flujo de autenticación
POST /patient/auth/request-otp — envia OTP al email
POST /patient/auth/verify-otp — retorna { token }
token como X-Patient-Token en cada request
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 / portal web de pacientes. Los endpoints de auth (/patient/auth/*) no requieren X-API-Key.
/patient/auth/request-otp
Envía un OTP de 6 dígitos al email del paciente.
Body (JSON)
{ "email": "paciente@example.com", "clinicSlug": "mi-clinica" }
Parámetros
| string | Requerido | Email del paciente | |
| clinicSlug | string | Requerido | Identificador único de la clínica |
Respuesta 200
{ "success": true, "message": "OTP sent to your email" }
Respuesta 404
{ "success": false, "error": "Patient not found" }
/patient/auth/verify-otp
Verifica el OTP y retorna un JWT de paciente (24 h).
Body (JSON)
{ "email": "paciente@example.com", "otp": "123456", "clinicSlug": "mi-clinica" }
Respuesta 200 — Token emitido
{ "success": true, "token": "eyJhbGciOiJIUzI1NiIs..." }
Respuesta 401
{ "success": false, "error": "Invalid or expired OTP" }
/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" } }