We do not handle life-threatening emergencies: call 112.

+34 611 673 280

DevelopersAPI v1

Guides

Visits

Create visits (every field, priorities, appointments, address and patient), read and list them with filters and pagination, get the estimated arrival time, cancel them and download the care report.

Last reviewed: October 1, 2026

A visit is a request for a healthcare professional to see a patient at home. This guide covers everything you can do with visits; the details of every field are also in the API reference.

MethodPathScopePurpose
POST/visitsvisits:writeCreate a visit
GET/visits/{id}visits:readRetrieve a visit
GET/visitsvisits:readList visits
GET/visits/{id}/etavisits:readEstimated arrival time
POST/visits/{id}/cancelvisits:writeCancel
GET/visits/{id}/reportreports:readCare report

Create a visit

POST /visits with the patient, the address, the service type and the reason. Always send a unique Idempotency-Key header for that request: if the connection drops and you repeat it, no duplicate visit is created (idempotency).

Shell
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"
  }'

The response is 201 Created with the full visit (see the visit object). If you repeat the same request with the same Idempotency-Key, you get 200 OK with the same visit and the Idempotent-Replayed: true header.

Request fields

FieldTypeRequiredDescription
patientobjectYes, unless you send patientIdPatient details. See the patient.
patientIduuidNoPatient from one of your earlier visits. If present, patient is not needed.
addressobjectYesAddress of the visit. See the address.
serviceTypeCodestringYes, or serviceTypeIdService type code, from the catalogue. Recommended: it's the same in every environment.
serviceTypeIduuidYes, or serviceTypeCodeService type id. It differs between environments; if both are sent, the code wins.
priorityURGENT | NORMAL | SCHEDULEDNoDefaults to NORMAL, or SCHEDULED if you send scheduledFrom. See priority and appointments.
scheduledFromISO 8601 date-timeAppointments onlyStart of the appointment, with a time zone (2026-10-02T09:00:00+02:00).
estimatedMinutesintegerNoEstimated duration: from 30 to 240, in steps of 10. Defaults to 60.
reasonstring (3–2000)YesReason for the request, as the patient or the referring doctor would describe it.
externalRefstring (≤ 100)If your contract requires itYour case number. See catalogue.
insurerIduuidIntermediaries onlyEnd insurer, from catalog.insurers.
callerNamestring (≤ 200)NoWho is requesting the visit, if not the patient ("Ana García (hija)", i.e. the patient's daughter).
callerPhonestringNoContact phone number of the requester. Same format as patient.phone.
scheduledTodate-timeNoDeprecated: use estimatedMinutes. If it's sent without it, the slot duration is rounded to the 10-minute step.

Request headers:

HeaderRequiredDescription
Idempotency-KeyRecommendedUnique identifier of the operation in your system (≤ 200 characters).
X-Sandbox-SimulationNoSandbox only: manual so that the visit doesn't move forward on its own.

Priority and appointments

priorityWhen to use itWhat happens
URGENTNon-life-threatening urgent case: the patient needs care as soon as possible (high fever, severe pain, a minor fall).It's assigned with priority to the first suitable unit available.
NORMALNeeds to be seen today, with no specific time.It's assigned according to the units' workload and routes.
SCHEDULEDAppointment at a specific time (wound care, check-ups, follow-ups).The unit arrives within the appointment slot.

For an appointment, send scheduledFrom (start) and estimatedMinutes (how long the care will take). We work out the end of the appointment: scheduledTo = scheduledFrom + estimatedMinutes.

JSON
{
  "priority": "SCHEDULED",
  "scheduledFrom": "2026-10-02T09:00:00+02:00",
  "estimatedMinutes": 40
}

Appointment rules:

  • scheduledFrom always includes a time zone (+02:00, Z). Without one, the server would interpret it as UTC.
  • It can't start more than 15 minutes in the past (/errors/visits/schedule-in-past): the small delay between the patient's call and the request is allowed, but scheduling past appointments is not.
  • It can't be more than 90 days ahead (/errors/visits/schedule-beyond-horizon).
  • estimatedMinutes goes from 30 to 240 in steps of 10 (30, 40, 50… 240). Any other value returns a validation 400. If you don't send it, 60 minutes is assumed.

The dispatch centre can extend the estimated time while the visit is in progress; the updated value is returned in estimatedMinutes when you retrieve the visit.

The address

FieldTypeRequiredDescription
line1string (1–255)YesStreet and number: Calle de Alcalá 120.
line2string (≤ 255)NoFloor, door, staircase: 3º B.
postalCodestring (4–10)YesPostcode: 28009.
citystring (1–100)YesMunicipality: Madrid.
provincestring (≤ 100)NoProvince.
accessNotesstring (≤ 1000)NoDirections: building entrance, door entry code, whether to call ahead, pets… Only the unit sees them.
latitude / longitudenumberNoCoordinates of the address, if you have them.

If you don't send coordinates, we geocode the address. If geocoding fails or the postcode doesn't match the street, the visit is still created and the dispatch centre reviews the address before assigning it, which may delay the unit's departure. To avoid this:

  • Keep the fields clean: the street number in line1, the floor in line2.
  • Send the correct postcode (it's the most common mistake).
  • If your system already has validated coordinates for the address, send them.

Coverage is Madrid and the Community of Madrid. If you're not sure whether an address is covered, ask us.

The patient

FieldTypeRequiredDescription
firstNamestring (1–100)YesFirst name.
lastNamestring (1–150)YesSurnames.
documentTypeDNI | NIE | PASSPORT | NONENoDefaults to NONE (minors without an ID card, or patients who don't provide one).
documentNumberstring (≤ 30)Yes, except with NONEDocument number. DNI and NIE numbers are validated (format and check letter). Not allowed with NONE.
birthDatedate YYYY-MM-DDRecommendedDate of birth: not in the future and less than 120 years ago.
sexMALE | FEMALE | OTHER | UNKNOWNNoRegistered sex (it's clinically relevant).
phonestringRecommendedThe patient's phone number, with or without the country code: +34600111222. The unit calls this number if it can't find the address.

How we identify the patient. Using the document type and number and the date of birth, we check whether the patient already exists in Salud en Ruta; if so, the visit is linked to them (so the professional has their history); if not, a new record is created. Without a document (NONE) there's no reliable way to tell whether it's the same person, so a new record is created every time. That's why we ask you to always send the document and date of birth when you have them. More details, and which data we return, in patients and privacy.

Patients from earlier visits. If a patient has already had a visit from you, you can send their patientId instead of patient. We only accept patients from your visits: a patientId that isn't returns a 404 (/errors/patients/not-found), just like one that doesn't exist. In practice, though, the simplest approach over the API is to send the patient's details again: the result is the same.

Common errors when creating a visit

StatustypeCause
400/errors/validation-errorA field has the wrong format. errors[] tells you which one (patient.documentNumber, estimatedMinutes…).
400/errors/provider-api/service-type-requiredYou sent neither serviceTypeCode nor serviceTypeId.
400/errors/provider/service-type-not-allowedThat service type isn't in your catalogue.
400/errors/visits/external-ref-requiredYour contract requires externalRef.
400/errors/provider/insurer-not-managedinsurerId isn't one of your insurers (or you aren't an intermediary).
400/errors/patients/document-invalidThe DNI or NIE isn't valid (format or check letter).
400/errors/visits/schedule-in-past · /errors/visits/schedule-beyond-horizonThe appointment is in the past or more than 90 days ahead.
409/errors/provider-api/idempotency-key-reusedYou reused an Idempotency-Key with a different body.

Every code is listed in errors.

The visit object

Every operation on a visit returns the same structure (inside data):

JSON
{
  "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"
}
FieldDescription
idVisit identifier. Use it in every other call. It differs between environments.
codeHuman-readable code (AV-000123): the one the dispatch centre will quote on the phone.
status / statusLabelStatus (lifecycle) and its label in the language of the request.
originHow it came in: PROVIDER_API, PROVIDER_PORTAL or INTRANET (by phone).
serviceDateDay of the service (YYYY-MM-DD, Madrid time).
milestonesTime of each milestone; null if it hasn't happened yet. It's the reliable source for what has happened.
unitOnly the unit type (MEDICAL/NURSING), from assignment onwards. Never names, licence plates or location.
outcomeResult once finished: ATTENDED, PATIENT_ABSENT (not at home), REFERRED_HOSPITAL (referred to hospital), REFUSED (refused care) or CANCELLED.
cancellableWhether you can cancel it through the API right now.
cancelledWithChargeWhether the cancellation is chargeable (only those made by the dispatch centre with the unit already on its way, as per your contract).
reportAvailableWhether there's a downloadable report.
invoiceIdThe invoice it's included in, once invoiced.

Retrieve a visit

GET /visits/{id} returns the visit with its timeline (timeline): every status change with its time.

Shell
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201" \
  -H "X-Api-Key: $SERAD_API_KEY"
JSON
{
  "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" }
  ]
}

A visit belonging to another provider returns 404, just like one that doesn't exist: we don't confirm the existence of visits that aren't yours.

List visits

GET /visits returns your visits, newest first, paginated.

Shell
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"
ParameterDescription
pagePage number, starting at 1. Defaults to 1.
limitVisits per page, from 1 to 100. Defaults to 10.
statusOne or more statuses, comma-separated or by repeating the parameter: status=PENDING,ASSIGNED.
priorityURGENT, NORMAL or SCHEDULED.
dateFrom / dateToService day from / to (inclusive), YYYY-MM-DD. They can't be in the future, and dateTo can't be earlier than dateFrom.
externalRefYour exact case number. It's how you find a visit without storing our id.
serviceTypeIdService type.
searchText matched against the visit code or the case number.
sortOrderDESC (default, newest first) or ASC, by request time.
JSON
{
  "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"
}

To go through them all, request pages until page = totalPages. If your goal is to keep your system in sync, use webhooks and keep the list for periodic reconciliation (for example, the day's visits every night).

Estimated arrival time

While the unit is en route (EN_ROUTE), GET /visits/{id}/eta works out how long it will take to arrive by road from its last known position:

Shell
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201/eta" \
  -H "X-Api-Key: $SERAD_API_KEY"
JSON
{
  "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"
}
  • In any other status, or if there's no recent position for the unit, etaMinutes and estimatedArrivalAt are null.
  • estimated: true means the road route couldn't be calculated and the figure is a straight-line approximation: allow a wider margin.
  • We never return the unit's position, only the time.
  • The ETA changes with traffic. If you show it to the patient, refresh it every few minutes, not every second (it counts towards your rate limit).

Cancel a visit

POST /visits/{id}/cancel with the reason:

Shell
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" }'
FieldTypeRequiredDescription
reasonstring (3–1000)YesReason for the cancellation. The dispatch centre sees it.

Rules:

  • You can only cancel through the API while the visit is PENDING or ASSIGNED, and then it's free of charge. The cancellable field in the response tells you whether it's possible right now.
  • With the unit en route or on site, the API responds 409 with the type /errors/provider/visit-not-cancellable. In that case, call the dispatch centre (+34 611 673 280): the unit has already left and needs to be told. Depending on your contract, that cancellation may be chargeable (cancelledWithCharge).
  • A visit that is already finished or cancelled can't be cancelled either (same 409).
  • When you cancel, you receive the visit.cancelled webhook.

Care report

Once the professional signs the visit report, you can download it as a PDF with GET /visits/{id}/report (scope reports:read), only if:

  1. the report is signed,
  2. Salud en Ruta has shared it with your organisation, and
  3. the patient has consented to it being sent to you.
Shell
curl "$SERAD_API_URL/visits/c6a3c348-40b6-4158-a44a-7d85f01a0201/report" \
  -H "X-Api-Key: $SERAD_API_KEY" \
  -o informe-AV-000123.pdf
  • The response is the PDF (Content-Type: application/pdf, Content-Disposition: attachment; filename="informe-AV-000123.pdf"), not JSON.
  • If any of the three conditions isn't met, it responds 404 with the type /errors/provider/report-not-available. Without the patient's consent there's no report, and we can't bypass that rule.
  • You'll know it's available from the report.shared webhook or from reportAvailable: true when you retrieve the visit.
  • Every download is logged as an access to clinical data, together with the key that made it. Download it once, store it in your document system with the safeguards the GDPR requires for health data, and don't request it again every time someone looks at it.

More on health data in patients and privacy.

Something missing or not matching what you see? Email our technical team: info@saludenruta.es.