Empezar
Inicio rápido
De cero a su primer paciente sincronizado en menos de cinco minutos. Todos los ejemplos usan datos del ambiente de pruebas.
Obtenga su llave de pruebas
En TotalMed entre a Configuración, Integraciones, Llaves de API y cree una llave. Las de pruebas empiezan con tm_test_ y operan sobre una clínica ficticia, con pacientes, citas y pagos de mentira.
La llave se muestra completa una sola vez. Guárdela en su gestor de secretos, nunca en el código.
Haga su primera llamada
Traiga los primeros pacientes de la clínica de pruebas.
curl https://api.totalmed.lat/v1/patients?limit=3 \
-H "Authorization: Bearer $TOTALMED_KEY"
const r = await fetch("https://api.totalmed.lat/v1/patients?limit=3", { headers: { Authorization: `Bearer ${process.env.TOTALMED_KEY}` } }); const pacientes = await r.json();
import os, requests r = requests.get( "https://api.totalmed.lat/v1/patients", params={"limit": 3}, headers={"Authorization": f"Bearer {os.environ['TOTALMED_KEY']}"}, ) pacientes = r.json()
Amarre su CRM al paciente
Cree el paciente y su identificador externo en una sola llamada. Si el contacto ya existe, la clave de idempotencia evita el duplicado.
curl https://api.totalmed.lat/v1/patients \ -H "Authorization: Bearer $TOTALMED_KEY" \ -H "Idempotency-Key: crm-8842-alta" \ -H "Content-Type: application/json" \ -d '{ "nombre": "Ana Quispe Rojas", "telefono": "+51987654321", "rut": "45872103", "email": "ana.quispe@ejemplo.pe", "external_ids": [{ "sistema": "hubspot", "external_id": "8842" }] }'
await fetch("https://api.totalmed.lat/v1/patients", { method: "POST", headers: { Authorization: `Bearer ${process.env.TOTALMED_KEY}`, "Idempotency-Key": "crm-8842-alta", "Content-Type": "application/json" }, body: JSON.stringify({ nombre: "Ana Quispe Rojas", telefono: "+51987654321", rut: "45872103", // DNI email: "ana.quispe@ejemplo.pe", external_ids: [{ sistema: "hubspot", external_id: "8842" }] }) });
requests.post(
"https://api.totalmed.lat/v1/patients",
headers={
"Authorization": f"Bearer {os.environ['TOTALMED_KEY']}",
"Idempotency-Key": "crm-8842-alta",
},
json={
"nombre": "Ana Quispe Rojas",
"telefono": "+51987654321",
"rut": "45872103", # DNI
"email": "ana.quispe@ejemplo.pe",
"external_ids": [{"sistema": "hubspot", "external_id": "8842"}],
},
)
Escuche los eventos
Registre su endpoint en Configuración, Integraciones, Webhooks y suscríbase a payment.confirmed. Desde ese momento su CRM se entera del dinero cobrado sin consultar nada.
Pruébelo ahora
Esta consola llama al ambiente de pruebas con una llave pública de demostración. Los datos son ficticios.
{
"data": [
{
"id": "3f9a1c74-2b08-4e15-9d33-5c1e77a0b412",
"nombre": "Ana Quispe Rojas",
"rut": "45872103",
"ruc": null,
"telefono": "+51987654321",
"email": "ana.quispe@ejemplo.pe",
"distrito": "Miraflores",
"etiquetas": ["VIP"],
"created_at": "2026-09-09T18:22:04-05:00"
},
{
"id": "7c1f6b20-9ae4-4f77-8b51-0d2a4e93c118",
"nombre": "Luis Ccahuana Ttito",
"rut": "09731556",
"ruc": "20548712399",
"telefono": "+51956120884",
"email": "luis.ccahuana@ejemplo.pe",
"distrito": "San Isidro",
"etiquetas": [],
"created_at": "2026-09-09T11:05:47-05:00"
}
]
}
Pulse Ejecutar para ver la respuesta del ambiente de pruebas.
Empezar
Autenticación
Llaves de API por clínica, con alcance acotado y revocables en cualquier momento.
Cómo se envía
Toda petición lleva la llave en la cabecera Authorization. No aceptamos la llave por parámetro de consulta, porque quedaría escrita en los registros de los servidores intermedios.
La clínica se deriva de la llave. Nunca hace falta enviar el identificador de la clínica.
Authorization: Bearer tm_live_9f3c1a7d4e…
Ambientes
Cada clínica tiene dos ambientes separados, con datos que no se mezclan nunca.
| Prefijo | Ambiente |
|---|---|
| tm_test_ | Pruebas. Clínica ficticia, facturación simulada, sin cobros reales |
| tm_live_ | Producción. Datos reales de pacientes y dinero real |
curl https://api.totalmed.lat/v1/whoami \ -H "Authorization: Bearer $TOTALMED_KEY" { "clinic_id": "9a2b4e01-…", "clinica": "Estética Lima Norte", "ambiente": "test", "pais": "PE", "moneda": "PEN", "zona_horaria": "America/Lima", "alcances": ["patients:read", "patients:write", "appointments:read"] }
Alcances
Cada llave declara qué puede hacer. Dé siempre el mínimo necesario. Un CRM que solo lee la agenda no necesita poder crear pagos.
| Alcance | Permite |
|---|---|
| patients:read | Listar y consultar pacientes |
| patients:write | Crear y actualizar pacientes |
| appointments:read | Consultar agenda y estados |
| appointments:write | Agendar, mover y cambiar estado |
| sales:read | Ventas de tratamientos y productos |
| payments:read | Pagos, abonos y saldos |
| payments:write | Registrar cobros |
| invoices:read | Comprobantes emitidos |
| invoices:write | Emitir boletas y facturas |
Rotación
Puede tener varias llaves activas a la vez. Para rotar sin cortar el servicio: cree la nueva, despliegue, confirme que la vieja dejó de usarse mirando su fecha de último uso, y recién ahí revóquela.
Si se filtra una llave
Revóquela desde Configuración. La revocación es inmediata, no hay caché. Después revise el registro de accesos de esa llave.
Empezar
Convenciones
Reglas transversales que valen para todos los recursos. Léalas una vez y no tendrá que volver.
Formatos
| Tipo | Formato |
|---|---|
| Fecha y hora | ISO 8601 con zona. Perú opera en America/Lima |
| Fecha | YYYY-MM-DD |
| Dinero | Decimal de dos posiciones, en la moneda de la clínica |
| Identificador | UUID versión 4 |
| Nulo | null explícito, el campo nunca se omite |
450.00, no como "450" ni como 45000.Filtros
Cualquier campo del recurso es filtrable, con el operador declarado en el propio valor.
# citas perdidas de septiembre GET /v1/appointments ?inicio=gte.2026-09-01 &inicio=lt.2026-10-01 &estado=eq.no_show # cobros confirmados de una sede GET /v1/payments ?pagado=eq.true &sede_id=eq.7c1f6b20-… &order=created_at.desc # buscar por teléfono, el + va escapado GET /v1/patients?telefono=eq.%2B51987654321
Operadores: eq, neq, gt, gte, lt, lte, like, ilike, in, is.
Consultas anidadas
Para no caer en el problema de N más uno, pida las relaciones en la misma llamada. Una sola petición devuelve la cita, su paciente, su venta y los pagos de esa venta.
Funciona hacia abajo y hacia arriba en la cadena.
GET /v1/appointments?select=
id,inicio,estado,
patients(id,nombre,telefono),
treatment_sales(
id,tratamiento,precio_total,abonado,
payments(id,monto,metodo,pagado)
)
&inicio=gte.2026-09-01
Paginación
Por defecto se devuelven 50 registros, con un máximo de 1000. El total viaja en la cabecera Content-Range, así no hace falta una llamada aparte para contar.
GET /v1/patients?limit=200&offset=0 Content-Range: 0-199/3482 GET /v1/patients?limit=200&offset=200 Content-Range: 200-399/3482
Límite de uso
600 peticiones por minuto y por clínica. Al pasarse recibe un 429 con la cabecera Retry-After en segundos.
Si necesita más para una carga inicial, avísenos y lo subimos por el tiempo que dure la migración.
Escrituras idempotentes
Envíe una clave propia en cada creación. Si la repite dentro de 24 horas, le devolvemos la respuesta original sin crear nada nuevo.
Úsela siempre. Es la diferencia entre un reintento inocente y una agenda con la misma cita tres veces.
Idempotency-Key: crm-8842-cita-2026-09-15
Errores
{
"error": {
"code": "validation_failed",
"message": "El campo inicio es obligatorio",
"field": "inicio"
}
}
| Código | Significado |
|---|---|
| 400 | Petición mal formada o validación fallida |
| 401 | Llave ausente, inválida o revocada |
| 403 | La llave no tiene ese alcance |
| 404 | No existe, o no pertenece a esta clínica |
| 409 | Conflicto, por ejemplo cupo ya tomado |
| 422 | Regla de negocio incumplida |
| 429 | Límite de peticiones excedido |
| 5xx | Error nuestro. Reintente con espera creciente |
Referencia
Pacientes
La persona. Raíz de toda la cadena de trazabilidad.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | Solo lectura |
| nombrereq | texto | Nombre completo |
| rut | texto | Documento de identidad. En Perú, el DNI de ocho dígitos |
| ruc | texto | Identificador tributario. Obligatorio para emitir factura |
| telefono | texto | Formato internacional, +51… |
| texto | ||
| fecha_nacimiento | fecha | |
| direccion | texto | |
| distrito_id | uuid | Distrito de residencia, para analizar de dónde llegan |
| etiquetas | lista | Texto libre, por ejemplo ["VIP"] |
| datos_extra | objeto | Campos personalizados que define cada clínica |
| external_ids | lista | Identificadores en sistemas externos |
| created_at | fecha y hora | Solo lectura |
GET /v1/patients?external_id=eq.hubspot:8842
GET /v1/patients/3f9a…/saldo
{
"patient_id": "3f9a…",
"moneda": "PEN",
"total_vendido": 2400.00,
"total_pagado": 1650.00,
"saldo_pendiente": 750.00,
"saldo_a_favor": 0.00
}
Referencia
Citas
La agenda. Incluye asistencia y ausencia, que es de donde sale la métrica que más duele en una clínica.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | |
| patient_idreq | uuid | |
| professional_id | uuid | |
| service_id | uuid | Define la duración por defecto |
| sede_id | uuid | Sede donde se atiende |
| inicioreq | fecha y hora | |
| fin | fecha y hora | Se calcula del servicio si no se envía |
| estado | enum | Ver abajo |
| sena_monto | decimal | Adelanto para reservar el cupo |
| sena_pagada | booleano | |
| notas | texto | Nota administrativa, no clínica |
Estados
| Estado | Significado |
|---|---|
| agendada | Reservada, sin confirmar |
| confirmada | El paciente confirmó |
| atendida | Se realizó. Suele traer venta asociada |
| no_show | No llegó y no avisó |
| cancelada | Se canceló con aviso |
curl https://api.totalmed.lat/v1/appointments \ -H "Authorization: Bearer $TOTALMED_KEY" \ -H "Idempotency-Key: crm-8842-cita-1" \ -d '{ "patient_id": "3f9a1c74-…", "professional_id": "b40d2e18-…", "service_id": "5e77aa03-…", "sede_id": "7c1f6b20-…", "inicio": "2026-09-15T10:30:00-05:00" }'
await tm.post("/appointments", { patient_id: "3f9a1c74-…", professional_id: "b40d2e18-…", service_id: "5e77aa03-…", sede_id: "7c1f6b20-…", inicio: "2026-09-15T10:30:00-05:00" }, { idempotencyKey: "crm-8842-cita-1" });
tm.post("/appointments", json={ "patient_id": "3f9a1c74-…", "professional_id": "b40d2e18-…", "service_id": "5e77aa03-…", "sede_id": "7c1f6b20-…", "inicio": "2026-09-15T10:30:00-05:00", }, idempotency_key="crm-8842-cita-1")
GET /v1/disponibilidad
?professional_id=b40d2e18-…
&service_id=5e77aa03-…
&fecha=2026-09-15
{
"fecha": "2026-09-15",
"zona_horaria": "America/Lima",
"cupos": [
"09:00", "09:45", "10:30", "15:00"
]
}
Referencia
Ventas de tratamiento
Lo que se vendió. Es el eslabón entre la cita y el dinero.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | |
| patient_idreq | uuid | |
| appointment_id | uuid | Cita que originó la venta |
| tratamientoreq | texto | Nombre del procedimiento vendido |
| precio_totalreq | decimal | |
| abonado | decimal | Suma de pagos confirmados. Solo lectura |
| fecha | fecha |
El saldo pendiente de una venta es precio_total menos abonado. No lo guardamos calculado, para que nunca quede desincronizado.
GET /v1/treatment_sales?select=
id,tratamiento,precio_total,abonado,
payments(id,monto,metodo,pagado,created_at)
&fecha=gte.2026-09-01
[
{
"id": "77bd4c91-…",
"tratamiento": "Rejuvenecimiento facial, 3 sesiones",
"precio_total": 2400.00,
"abonado": 1650.00,
"payments": [
{ "id": "c4e1…", "monto": 1200.00,
"metodo": "tarjeta", "pagado": true },
{ "id": "d8f2…", "monto": 450.00,
"metodo": "efectivo", "pagado": true }
]
}
]
Referencia
Ventas de producto
Retail dentro de la clínica. Cremas, protectores, suplementos. Descuenta inventario sola.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | |
| patient_id | uuid | Puede ir vacío en una venta de mostrador |
| professional_id | uuid | Quien la vendió, para comisiones |
| product_idreq | uuid | |
| payment_id | uuid | Pago que la cubre |
| cantidadreq | decimal | Admite fracciones, por ejemplo media ampolla |
| precio_unitarioreq | decimal | |
| fecha | fecha |
/v1/products antes si su flujo lo necesita.GET /v1/products?activo=eq.true
[
{
"id": "a19c…",
"nombre": "Protector solar SPF 50+",
"precio": 89.00,
"stock": 34,
"stock_minimo": 10
}
]
Referencia
Pagos
El dinero. El recurso más delicado de la API, y donde más integraciones se equivocan.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | |
| patient_idreq | uuid | Siempre presente |
| sale_id | uuid | Venta de tratamiento imputada |
| record_id | uuid | Expediente quirúrgico imputado |
| sede_id | uuid | Dónde se cobró |
| montoreq | decimal | |
| metodo | enum | efectivo, tarjeta, transferencia, mercadopago |
| pagado | booleano | Verdadero solo cuando el dinero entró |
| descuento_codigo | texto | |
| descuento_monto | decimal | |
| created_at | fecha y hora |
pagado en falso no es dinero. Es una intención de cobro, por ejemplo un enlace enviado que el paciente todavía no abrió. Para conciliar ingresos reales filtre siempre por pagado=eq.true, o escuche el evento payment.confirmed.Los tres caminos de un pago
| Situación | Cómo se ve |
|---|---|
| Paga un tratamiento | sale_id con valor |
| Paga una cirugía | record_id con valor |
| Abona a cuenta | Ambos en null. Es saldo a favor |
GET /v1/payments?select=monto,metodo,sede_id,created_at &pagado=eq.true &created_at=gte.2026-09-01T00:00:00-05:00 &created_at=lt.2026-10-01T00:00:00-05:00 &order=created_at.asc
Referencia
Comprobantes
Boleta y factura electrónica. En Perú se emiten a SUNAT a través de su operador de servicios electrónicos.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| id | uuid | |
| patient_id | uuid | |
| payment_id | uuid | Pago que respalda el comprobante |
| dte_tipo | enum | boleta, factura, nota_credito |
| monto | decimal | Incluye IGV |
| folio | texto | Serie y número, por ejemplo F001-00004821 |
| pdf_url | texto | Representación impresa |
| emitida_at | fecha y hora |
Perú
- Operadores soportados: Nubefact y FactuSmart.
- IGV del 18 por ciento, calculado por nosotros.
- Boleta de venta electrónica para persona natural, con DNI.
- Factura electrónica para empresa, exige RUC válido de once dígitos.
- La clínica carga sus propias credenciales del operador en Configuración.
POST /v1/facturacion/emitir
{
"payment_id": "c4e1a802-…",
"tipo": "factura"
}
respuesta
{
"invoice_id": "e05b…",
"dte_tipo": "factura",
"folio": "F001-00004821",
"monto": 1200.00,
"igv": 183.05,
"aceptada": true,
"pdf_url": "https://…/F001-00004821.pdf"
}
aceptada. Si viene en falso, el folio llega en null, el comprobante queda registrado de forma interna y el campo motivo explica el rechazo.Referencia
Identificadores externos
El puente entre su CRM y TotalMed. Amarra cualquier entidad nuestra con su equivalente en otro sistema.
La cadena completa
Este es el recorrido que permite seguir a una persona desde que su CRM la captó hasta el comprobante emitido.
contact_id su CRM └─▸ patient_id patients.id └─▸ appointment_id appointments.patient_id └─▸ sale_id treatment_sales.appointment_id └─▸ payment_id payments.sale_id └─▸ invoice_id invoices.payment_id
Cada webhook que le enviamos ya trae esta cadena resuelta. Su CRM no necesita consultas extra para saber a qué contacto pertenece un pago.
Campos
| Campo | Tipo | Notas |
|---|---|---|
| entidadreq | enum | patient, appointment, treatment_sale, product_sale, payment, invoice |
| entidad_idreq | uuid | El identificador nuestro |
| sistemareq | texto | Nombre de su sistema, texto libre |
| external_idreq | texto | El identificador suyo |
POST /v1/external_ids
{
"entidad": "patient",
"entidad_id": "3f9a1c74-…",
"sistema": "hubspot",
"external_id": "8842"
}
Eventos
Webhooks
Le avisamos en el momento en que algo pasa, para que su CRM no tenga que consultar en bucle.
Los doce eventos
Qué le llega
Cada envío trae la cadena de identificadores completa hasta la raíz, más los identificadores de su propio sistema si están amarrados.
Responda 2xx dentro de diez segundos. Procese después, en su propia cola. No nos haga esperar a que termine su lógica.
{
"id": "evt_01J8X3QK7ZC4",
"type": "payment.confirmed",
"created_at": "2026-09-10T15:42:11-05:00",
"clinic_id": "9a2b4e01-…",
"data": {
"payment_id": "c4e1a802-…",
"patient_id": "3f9a1c74-…",
"sale_id": "77bd4c91-…",
"appointment_id": "12ac5f38-…",
"sede_id": "7c1f6b20-…",
"monto": 450.00,
"moneda": "PEN",
"metodo": "tarjeta",
"pagado": true
},
"external_ids": {
"patient": { "hubspot": "8842" }
}
}
Verificar la firma
Firmamos cada envío con HMAC SHA-256 sobre la cadena timestamp + "." + cuerpo crudo, usando el secreto de su endpoint.
Verifique siempre. Un endpoint público sin verificación de firma acepta cualquier cosa que le manden.
X-TotalMed-Event: payment.confirmed X-TotalMed-Delivery: dlv_01J8X3QK7ZC4 X-TotalMed-Timestamp: 1789412531 X-TotalMed-Signature: sha256=8f2c1b…
const crypto = require("crypto"); function verificar(cuerpoCrudo, h, secreto) { const ts = h["x-totalmed-timestamp"]; const firma = h["x-totalmed-signature"].replace("sha256=", ""); const esperado = crypto .createHmac("sha256", secreto) .update(`${ts}.${cuerpoCrudo}`) .digest("hex"); // comparación en tiempo constante const ok = crypto.timingSafeEqual( Buffer.from(firma), Buffer.from(esperado) ); // ventana de 5 minutos contra reenvíos const fresco = Math.abs(Date.now()/1000 - Number(ts)) < 300; return ok && fresco; }
import hmac, hashlib, time def verificar(cuerpo_crudo: bytes, h: dict, secreto: str) -> bool: ts = h["x-totalmed-timestamp"] firma = h["x-totalmed-signature"].replace("sha256=", "") esperado = hmac.new( secreto.encode(), f"{ts}.".encode() + cuerpo_crudo, hashlib.sha256, ).hexdigest() # comparación en tiempo constante ok = hmac.compare_digest(firma, esperado) # ventana de 5 minutos contra reenvíos fresco = abs(time.time() - int(ts)) < 300 return ok and fresco
Entrega y reintentos
| Intento | Cuándo |
|---|---|
| 1 | Inmediato |
| 2 | 1 minuto después |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 6 horas |
| 7 | 24 horas |
Agotados los intentos, el envío queda marcado como fallido y visible en el registro de la clínica, desde donde se puede reenviar a mano.
X-TotalMed-Delivery y descarte los repetidos, o su CRM va a duplicar ingresos.payment.confirmed antes que payment.created. Ordene por created_at, nunca por orden de llegada.Registro de envíos
GET /v1/webhook_deliveries
?event_type=eq.payment.confirmed
&status=eq.failed
POST /v1/webhook_deliveries/dlv_01J8X3…/reenviar
Guardamos treinta días de historial, con el cuerpo enviado, el código de respuesta y el número de intentos.
Guías
Recetas completas
Flujos de punta a punta, no fragmentos sueltos. Cada uno resuelve un problema real de operación.
Integración
Sincronizar un CRM
Carga inicial por lotes, amarre de identificadores, reconciliación de duplicados y encendido gradual de webhooks.
Ver la recetaOperación
Reaccionar a una ausencia
Escuchar el cambio de estado, disparar la recuperación en su CRM y medir cuántas ausencias logró rescatar.
Ver la recetaFinanzas
Conciliar el mes
Traer solo el dinero confirmado, separarlo por sede y medio de pago, y cuadrarlo contra los comprobantes emitidos.
Ver la recetaMigración
Migrar la cartera
Subir miles de pacientes sin duplicar, con clave de idempotencia y límite de uso ampliado durante la carga.
Ver la recetaGuías · Integración
Sincronizar un CRM
De cero a un CRM que sabe, en tiempo real, cuánto vale cada contacto que captó.
Trabaje contra pruebas
Pida una llave tm_test_. La clínica de pruebas trae 347 pacientes ficticios, agenda de tres meses y pagos en soles. Nada de lo que haga ahí toca datos reales.
Suba su cartera con idempotencia
Recorra los contactos de su CRM y cree cada paciente con una clave derivada de su propio identificador. Si el proceso se cae a la mitad, lo vuelve a correr entero sin duplicar a nadie.
for (const c of contactosDelCrm) { await tm.post("/patients", { nombre: c.nombre, telefono: c.telefono, email: c.email, rut: c.dni, external_ids: [{ sistema: "hubspot", external_id: c.id }] }, { idempotencyKey: `hubspot-${c.id}-alta` }); }
Reconcilie antes de encender nada
Compare ambos lados y liste los casos raros: pacientes de TotalMed sin contacto en el CRM, contactos con dos pacientes, y teléfonos repetidos. Resuélvalos a mano ahora, no en producción con webhooks corriendo.
GET /v1/patients?external_id=is.null&limit=1000
Encienda los eventos de a poco
Empiece solo con patient.created y appointment.status_changed. Cuando lleve una semana sin envíos fallidos, agregue payment.confirmed y invoice.issued.
Cierre el círculo
Con la cadena amarrada, su CRM ya puede responder la pregunta que importa: cuánto facturó realmente cada campaña, cada canal y cada vendedor. No cuántos formularios llenaron.
// al recibir payment.confirmed const contactId = evento.external_ids?.patient?.hubspot; if (contactId) { await crm.sumarIngreso(contactId, evento.data.monto, { moneda: evento.data.moneda, sede: evento.data.sede_id, fecha: evento.created_at }); }
Referencia general
OpenAPI y Postman
La especificación completa, para cargarla en sus propias herramientas en lugar de leer una página.
OpenAPI 3.1
Especificación
Los 21 endpoints y los 5 webhooks descritos a nivel de campo, con ejemplos de petición y respuesta. Genera clientes en cualquier lenguaje.
Postman
Colección
Nueve carpetas y treinta peticiones listas para importar, con respuestas de ejemplo guardadas y los cuerpos reales de cada webhook.
Cómo usar la colección
- Importe el archivo en Postman.
- Abra las variables de la colección.
- Ponga su llave en
api_key. Las de pruebas empiezan contm_test_. - Deje
base_urlapuntando al ambiente de pruebas mientras desarrolla. - Empiece por la carpeta 00, Empezar aquí.
La autenticación ya viene configurada a nivel de colección, así que no hace falta poner la cabecera en cada petición.
Probar sus webhooks sin esperar
La carpeta 08, Payloads de ejemplo trae los cuerpos exactos que enviamos, con sus cabeceras de firma. Apunte la variable webhook_url a su receptor y dispárelos.
Así valida su integración hoy, sin tener que provocar una venta real para ver qué llega.
Generar un cliente
La especificación es OpenAPI 3.1 estándar. Estas dos herramientas cubren la mayoría de los casos.
npx openapi-typescript https://developers.totalmed.lat/openapi.yaml -o src/totalmed.d.ts
openapi-generator-cli generate -i https://developers.totalmed.lat/openapi.yaml -g python -o ./totalmed-client
Versionado
La especificación vive en la misma dirección y se actualiza con cada cambio. Los cambios que rompen compatibilidad se anuncian con noventa días de anticipación y nunca entran en la versión 1.
https://developers.totalmed.lat/openapi.yaml
Referencia general
Registro de cambios
Qué cambió y cuándo. Los cambios que rompen compatibilidad se anuncian con noventa días de anticipación.
Webhooks salientes
Doce eventos, firma HMAC SHA-256, siete intentos de entrega y registro de envíos con reenvío manual.
Llaves de API e identificadores externos
Autenticación servidor a servidor con alcances por recurso, y el recurso external_ids para amarrar sistemas externos a cualquier entidad.
Inventario conectado a las ventas
Registrar una venta de producto descuenta existencias. Si no hay stock, la respuesta pasa a ser 422.
Fechas de atención sin desfase
Las atenciones registradas cerca de medianoche podían quedar con la fecha del día anterior en zonas horarias distintas de la clínica.
RUC y campos personalizados en el paciente
Se agregan ruc para emitir factura y datos_extra para los campos que define cada clínica.
Montos con decimales
El dinero pasa a decimal de dos posiciones en toda la API, para soportar soles, bolivianos y el resto de monedas de la región.
Referencia general
Estado del servicio
Disponibilidad de los últimos noventa días, medida cada minuto desde tres regiones.
Componentes
| Componente | Estado | 90 días |
|---|---|---|
| API REST | Operativo | 99,98 % |
| Entrega de webhooks | Operativo | 99,94 % |
| Emisión a SUNAT | Operativo | 99,71 % |
| Aplicación web | Operativo | 99,99 % |
La emisión depende del operador de servicios electrónicos y de SUNAT. Sus caídas se reflejan acá aunque no sean nuestras.
API REST, últimos 90 días
Hace 90 días · hoy
Último incidente
Latencia elevada, 41 minutos
Una consulta sin índice en el reporte de rentabilidad saturó la base. Se agregó el índice y se movió el reporte a una réplica de lectura.