Guías
Avisos
Crear avisos (todos los campos, prioridades, citas, dirección y paciente), consultarlos, listarlos con filtros y paginación, conocer la hora estimada de llegada, cancelarlos y descargar el informe asistencial.
Última revisión: 1 de octubre de 2026
Un aviso es una petición de visita de un profesional sanitario al domicilio de un paciente. Esta guía cubre todo lo que puedes hacer con ellos; los detalles de cada campo también están en la referencia de la API.
| Método | Ruta | Ámbito | Para qué |
|---|---|---|---|
POST | /visits | visits:write | Crear un aviso |
GET | /visits/{id} | visits:read | Consultar un aviso |
GET | /visits | visits:read | Listar avisos |
GET | /visits/{id}/eta | visits:read | Hora estimada de llegada |
POST | /visits/{id}/cancel | visits:write | Cancelar |
GET | /visits/{id}/report | reports:read | Informe asistencial |
Crear un aviso
POST /visits con el paciente, el domicilio, el tipo de servicio y el motivo. Envía siempre una cabecera Idempotency-Key única para esa alta: si la conexión se corta y repites la petición, no se crea un aviso duplicado (idempotencia).
curl -X POST "$SERAD_API_URL/visits" \
-H "X-Api-Key: $SERAD_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: crm-exp-2026-004512-1" \
-d '{
"patient": {
"firstName": "María",
"lastName": "García López",
"documentType": "DNI",
"documentNumber": "12345678Z",
"birthDate": "1948-05-17",
"sex": "FEMALE",
"phone": "+34600111222"
},
"address": {
"line1": "Calle de Alcalá 120",
"line2": "3º B",
"postalCode": "28009",
"city": "Madrid",
"province": "Madrid",
"accessNotes": "Portal 2, código 1234#. Ascensor al fondo."
},
"serviceTypeCode": "GENERAL_CONSULTATION",
"priority": "URGENT",
"reason": "Fiebre alta desde ayer, 39 ºC, malestar general",
"externalRef": "EXP-2026-004512",
"callerName": "Ana García (hija)",
"callerPhone": "+34600111333"
}'La respuesta es 201 Created con el aviso completo (ver la respuesta del aviso). Si repites la misma petición con la misma Idempotency-Key, recibes 200 OK con el mismo aviso y la cabecera Idempotent-Replayed: true.
Campos del alta
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
patient | objeto | Sí, salvo que envíes patientId | Datos del paciente. Ver el paciente. |
patientId | uuid | No | Paciente de un aviso anterior tuyo. Si llega, patient no hace falta. |
address | objeto | Sí | Domicilio de la visita. Ver la dirección. |
serviceTypeCode | string | Sí, o serviceTypeId | Código del tipo de servicio, del catálogo. Recomendado: es igual en todos los entornos. |
serviceTypeId | uuid | Sí, o serviceTypeCode | Id del tipo de servicio. Cambia entre entornos; si llegan los dos, manda el código. |
priority | URGENT | NORMAL | SCHEDULED | No | Por defecto NORMAL, o SCHEDULED si envías scheduledFrom. Ver prioridad y citas. |
scheduledFrom | fecha-hora ISO 8601 | Solo en citas | Inicio de la cita, con zona horaria (2026-10-02T09:00:00+02:00). |
estimatedMinutes | entero | No | Duración estimada: de 30 a 240, en bloques de 10. Por defecto, 60. |
reason | string (3–2000) | Sí | Motivo de la solicitud, tal y como lo describiría el paciente o el médico que lo deriva. |
externalRef | string (≤ 100) | Si tu contrato lo exige | Tu nº de expediente. Ver catálogo. |
insurerId | uuid | Solo intermediarios | Aseguradora final, de catalog.insurers. |
callerName | string (≤ 200) | No | Quién solicita el aviso, si no es el paciente («Ana García (hija)»). |
callerPhone | string | No | Teléfono de contacto de quien solicita. Mismo formato que patient.phone. |
scheduledTo | fecha-hora | No | Obsoleto: usa estimatedMinutes. Si llega sin él, la duración de la franja se redondea al bloque de 10 minutos. |
Cabeceras del alta:
| Cabecera | Obligatoria | Descripción |
|---|---|---|
Idempotency-Key | Recomendada | Identificador único de la operación en tu sistema (≤ 200 caracteres). |
X-Sandbox-Simulation | No | Solo sandbox: manual para que el aviso no avance solo. |
Prioridad y citas
priority | Cuándo usarla | Qué pasa |
|---|---|---|
URGENT | Urgencia no vital: el paciente necesita atención lo antes posible (fiebre alta, dolor intenso, caída leve). | Se asigna con preferencia a la primera unidad compatible disponible. |
NORMAL | Hay que atenderlo hoy, sin hora concreta. | Se asigna según la carga y la ruta de las unidades. |
SCHEDULED | Cita a una hora concreta (curas, controles, seguimiento). | La unidad llega dentro de la franja de la cita. |
Para una cita, envía scheduledFrom (inicio) y estimatedMinutes (cuánto durará la atención). El fin de la cita lo calculamos nosotros: scheduledTo = scheduledFrom + estimatedMinutes.
{
"priority": "SCHEDULED",
"scheduledFrom": "2026-10-02T09:00:00+02:00",
"estimatedMinutes": 40
}Reglas de la cita:
scheduledFromlleva siempre zona horaria (+02:00,Z). Sin ella, el servidor la interpretaría en UTC.- No puede empezar más de 15 minutos en el pasado (
/errors/visits/schedule-in-past): se admite el pequeño retraso entre la llamada del paciente y el alta, pero no programar citas pasadas. - No puede estar a más de 90 días vista (
/errors/visits/schedule-beyond-horizon). estimatedMinutesva de 30 a 240 en bloques de 10 (30, 40, 50… 240). Cualquier otro valor responde400de validación. Si no lo envías, se asumen 60 minutos.
El tiempo estimado se puede ampliar en la centralita con el aviso en curso; el valor actualizado sale en estimatedMinutes al consultar el aviso.
La dirección
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
line1 | string (1–255) | Sí | Calle y número: Calle de Alcalá 120. |
line2 | string (≤ 255) | No | Piso, puerta, escalera: 3º B. |
postalCode | string (4–10) | Sí | Código postal: 28009. |
city | string (1–100) | Sí | Municipio: Madrid. |
province | string (≤ 100) | No | Provincia. |
accessNotes | string (≤ 1000) | No | Indicaciones para llegar: portal, código del portero, si hay que llamar antes, animales… Solo las ve la unidad. |
latitude / longitude | número | No | Coordenadas del domicilio, si las tienes. |
Si no envías coordenadas, geocodificamos la dirección. Si la geocodificación falla o el código postal no cuadra con la calle, el aviso se crea igualmente y la centralita revisa la dirección antes de asignarlo, lo que puede retrasar la salida. Para evitarlo:
- Separa bien los campos: el número en
line1, el piso enline2. - Envía el código postal correcto (es lo que más falla).
- Si tu sistema ya tiene las coordenadas validadas del domicilio, envíalas.
La cobertura es Madrid y la Comunidad de Madrid. Si dudas de si una dirección está cubierta, pregúntanos.
El paciente
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
firstName | string (1–100) | Sí | Nombre. |
lastName | string (1–150) | Sí | Apellidos. |
documentType | DNI | NIE | PASSPORT | NONE | No | Por defecto NONE (menores sin DNI, o quien no lo aporta). |
documentNumber | string (≤ 30) | Sí, salvo NONE | Nº de documento. El DNI y el NIE se validan (formato y letra). Con NONE no se admite. |
birthDate | fecha AAAA-MM-DD | Recomendado | Fecha de nacimiento: no futura y de hace menos de 120 años. |
sex | MALE | FEMALE | OTHER | UNKNOWN | No | Sexo registrado (es relevante clínicamente). |
phone | string | Recomendado | Teléfono del paciente, con o sin prefijo: +34600111222. La unidad llama a este número si no encuentra el domicilio. |
Cómo identificamos al paciente. Con el tipo y nº de documento y la fecha de nacimiento buscamos si el paciente ya existe en Salud en Ruta; si existe, el aviso se le asocia (así el profesional tiene su historia); si no, se crea. Sin documento (NONE) no hay forma segura de saber si es la misma persona, así que se crea una ficha nueva cada vez. Por eso te pedimos que envíes siempre el documento y la fecha de nacimiento cuando los tengas. Más detalles, y qué datos devolvemos, en pacientes y privacidad.
Paciente de avisos anteriores. Si un paciente ya tuvo un aviso tuyo, puedes enviar su patientId en lugar de patient. Solo admitimos pacientes de tus avisos: un patientId que no lo sea responde 404 (/errors/patients/not-found), igual que uno que no existe. Lo más sencillo por API, de todas formas, es volver a enviar los datos del paciente: el resultado es el mismo.
Errores frecuentes del alta
| Estado | type | Causa |
|---|---|---|
400 | /errors/validation-error | Un campo no cumple el formato. errors[] dice cuál (patient.documentNumber, estimatedMinutes…). |
400 | /errors/provider-api/service-type-required | No has enviado ni serviceTypeCode ni serviceTypeId. |
400 | /errors/provider/service-type-not-allowed | Ese tipo de servicio no está en tu catálogo. |
400 | /errors/visits/external-ref-required | Tu contrato exige externalRef. |
400 | /errors/provider/insurer-not-managed | insurerId no es una de tus aseguradoras (o no eres intermediario). |
400 | /errors/patients/document-invalid | El DNI o NIE no es válido (formato o letra de control). |
400 | /errors/visits/schedule-in-past · /errors/visits/schedule-beyond-horizon | La cita está en el pasado o a más de 90 días. |
409 | /errors/provider-api/idempotency-key-reused | Has reutilizado una Idempotency-Key con otro cuerpo. |
Todos los códigos, en errores.
La respuesta del aviso
Todas las operaciones sobre un aviso devuelven la misma estructura (dentro de data):
{
"id": "c6a3c348-40b6-4158-a44a-7d85f01a0201",
"code": "AV-000123",
"status": "EN_ROUTE",
"statusLabel": "En camino",
"priority": "URGENT",
"origin": "PROVIDER_API",
"externalRef": "EXP-2026-004512",
"serviceType": { "id": "57605fe9-bade-4040-b403-5eeb58e57c9e", "code": "GENERAL_CONSULTATION", "name": "Consulta general" },
"requiredProfessional": "DOCTOR",
"insurer": null,
"patientName": "María García López",
"address": { "line1": "Calle de Alcalá 120", "city": "Madrid", "postalCode": "28009" },
"serviceDate": "2026-10-01",
"scheduledFrom": null,
"scheduledTo": null,
"estimatedMinutes": 60,
"milestones": {
"requestedAt": "2026-10-01T09:10:00.000Z",
"assignedAt": "2026-10-01T09:15:00.000Z",
"departedAt": "2026-10-01T09:32:00.000Z",
"arrivedAt": null,
"finishedAt": null,
"cancelledAt": null,
"invoicedAt": null
},
"unit": { "type": "MEDICAL", "label": "Unidad médica" },
"outcome": null,
"cancellationReason": null,
"cancelledWithCharge": false,
"cancellable": false,
"reportAvailable": false,
"invoiceId": null,
"createdAt": "2026-10-01T09:10:00.000Z",
"updatedAt": "2026-10-01T09:32:00.000Z"
}| Campo | Descripción |
|---|---|
id | Identificador del aviso. Úsalo en las demás llamadas. Cambia entre entornos. |
code | Código legible (AV-000123): el que te dirá la centralita por teléfono. |
status / statusLabel | Estado (ciclo de vida) y su texto en el idioma de la petición. |
origin | Por dónde entró: PROVIDER_API, PROVIDER_PORTAL o INTRANET (por teléfono). |
serviceDate | Día del servicio (AAAA-MM-DD, hora de Madrid). |
milestones | Hora de cada hito; null si aún no ha ocurrido. Es la fuente fiable para saber qué ha pasado. |
unit | Solo el tipo de unidad (MEDICAL/NURSING) desde la asignación. Nunca nombres, matrículas ni posición. |
outcome | Resultado al finalizar: ATTENDED (atendido), PATIENT_ABSENT (no estaba), REFERRED_HOSPITAL (derivado al hospital), REFUSED (rechazó la atención) o CANCELLED. |
cancellable | Si puedes cancelarlo ahora mismo por la API. |
cancelledWithCharge | Si la cancelación tiene coste (solo las que hace la centralita con la unidad ya en camino, según tu contrato). |
reportAvailable | Si hay informe descargable. |
invoiceId | Factura en la que está incluido, cuando se factura. |
Consultar un aviso
GET /visits/{id} devuelve el aviso con su línea de tiempo (timeline): cada cambio de estado con su hora.
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201" \
-H "X-Api-Key: $SERAD_API_KEY"{
"timeline": [
{ "status": "PENDING", "label": "Aviso recibido", "at": "2026-10-01T09:10:00.000Z" },
{ "status": "ASSIGNED", "label": "Unidad asignada", "at": "2026-10-01T09:15:00.000Z" },
{ "status": "EN_ROUTE", "label": "Unidad en camino", "at": "2026-10-01T09:32:00.000Z" }
]
}Un aviso de otro proveedor responde 404, igual que uno que no existe: no confirmamos que existan avisos que no son tuyos.
Listar avisos
GET /visits devuelve tus avisos, del más reciente al más antiguo, paginados.
curl "$SERAD_API_URL/visits?status=PENDING,ASSIGNED,EN_ROUTE&dateFrom=2026-10-01&page=1&limit=50" \
-H "X-Api-Key: $SERAD_API_KEY"| Parámetro | Descripción |
|---|---|
page | Página, desde 1. Por defecto 1. |
limit | Avisos por página, de 1 a 100. Por defecto 10. |
status | Uno o varios estados, separados por comas o repitiendo el parámetro: status=PENDING,ASSIGNED. |
priority | URGENT, NORMAL o SCHEDULED. |
dateFrom / dateTo | Día de servicio desde / hasta (incluidos), AAAA-MM-DD. No pueden ser futuros, y dateTo no puede ser anterior a dateFrom. |
externalRef | Tu nº de expediente exacto. Es la forma de encontrar un aviso sin guardar nuestro id. |
serviceTypeId | Tipo de servicio. |
search | Texto en el código del aviso o en el nº de expediente. |
sortOrder | DESC (por defecto, más recientes primero) o ASC, por hora de solicitud. |
{
"status": 200,
"data": {
"items": [ { "id": "c6a3c348-…", "code": "AV-000123", "status": "EN_ROUTE", "…": "…" } ],
"pagination": { "page": 1, "limit": 50, "totalItems": 134, "totalPages": 3 }
},
"message": "Operación realizada correctamente"
}Para recorrer todos, pide páginas hasta page = totalPages. Si tu objetivo es mantener tu sistema sincronizado, usa los webhooks y deja el listado para una reconciliación periódica (por ejemplo, cada noche, los avisos del día).
Hora estimada de llegada
Con la unidad en camino (EN_ROUTE), GET /visits/{id}/eta calcula cuánto le falta para llegar, por carretera, desde su última posición conocida:
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201/eta" \
-H "X-Api-Key: $SERAD_API_KEY"{
"status": 200,
"data": {
"status": "EN_ROUTE",
"etaMinutes": 14,
"estimatedArrivalAt": "2026-10-01T09:46:00.000Z",
"estimated": false,
"calculatedAt": "2026-10-01T09:32:10.000Z"
},
"message": "Operación realizada correctamente"
}- En cualquier otro estado, o si no hay posición reciente de la unidad,
etaMinutesyestimatedArrivalAtsonnull. estimated: trueindica que no se pudo calcular la ruta por carretera y la cifra es una aproximación en línea recta: tómala con más margen.- Nunca devolvemos la posición de la unidad, solo el tiempo.
- La ETA cambia con el tráfico. Si la enseñas al paciente, refréscala cada pocos minutos, no cada segundo (cuenta para tu límite de peticiones).
Cancelar un aviso
POST /visits/{id}/cancel con el motivo:
curl -X POST "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201/cancel" \
-H "X-Api-Key: $SERAD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "El paciente ya no necesita la visita" }'| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
reason | string (3–1000) | Sí | Motivo de la cancelación. Lo ve la centralita. |
Reglas:
- Solo se puede cancelar por la API mientras el aviso está
PENDINGoASSIGNED, y entonces no tiene coste. El campocancellablede la respuesta te dice si se puede en este momento. - Con la unidad en camino o en el domicilio, la API responde
409con el tipo/errors/provider/visit-not-cancellable. En ese caso llama a la centralita (+34 611 673 280): la unidad ya ha salido y hay que avisarla. Según tu contrato, esa cancelación puede tener coste (cancelledWithCharge). - Un aviso ya finalizado o cancelado tampoco se puede cancelar (mismo
409). - Al cancelar recibes el webhook
visit.cancelled.
Informe asistencial
Cuando el profesional firma el informe de la visita, puedes descargarlo en PDF con GET /visits/{id}/report (ámbito reports:read), solo si:
- el informe está firmado,
- Salud en Ruta lo ha compartido con tu organización, y
- el paciente ha consentido que se te envíe.
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201/report" \
-H "X-Api-Key: $SERAD_API_KEY" \
-o informe-AV-000123.pdf- La respuesta es el PDF (
Content-Type: application/pdf,Content-Disposition: attachment; filename="informe-AV-000123.pdf"), no JSON. - Si no se cumple alguna de las tres condiciones, responde
404con el tipo/errors/provider/report-not-available. Sin consentimiento del paciente no hay informe, y no podemos saltarnos esa regla. - Sabrás que está disponible por el webhook
report.sharedo porreportAvailable: trueal consultar el aviso. - Cada descarga queda registrada como acceso a datos clínicos, con la clave que la hizo. Descárgalo una vez, guárdalo en tu sistema documental con las medidas que exige el RGPD para datos de salud, y no lo vuelvas a pedir en cada consulta.
Más sobre los datos de salud en pacientes y privacidad.
¿Falta algo o algo no cuadra con lo que ves? Escribe al equipo técnico: info@saludenruta.es.
