Medical Core Docs
POST/appointments
Citas

Crear una cita

Crea una nueva cita médica (programada o directa).

Crea una cita. La cita puede ser programada (`type: "p"`) o directa (`type: "d"`). El backend reenvía el payload a MedicalCore RD, que es extremadamente estricto con la estructura: un campo extra en `paciente` o un `area._id` mal mapeado devuelve 510 Not Extended. Headers obligatorios: `Content-Type: application/json`, `x-api-key: <MEDEX_API_KEY>`, `enterpriseid: 150575`, `ownerid: 150575`.
Opción A (recomendada): envía `pacienteCedula` en la raíz y el backend busca el `raw` automáticamente.
Opción B (más rápida, validada): envía `citaPaciente.paciente` con el objeto `raw` saneado (sólo 11 campos). Evita llamadas extra al backend.
Mapeo crítico: `cita.area._id` debe ser el área de CONSULTA (660, 1816, 622), NO la specialty (1704, 1690, 1723). El wrapper `/areas` expone especialidades; las áreas de consulta son un subconjunto interno.
Timezone: `cita.start` en formato `-04:00` (AST) y `cita.end` en formato `Z` (UTC). Si se calcula mal → 510 "la fecha fin debe ser mayor a la fecha de inicio".
Saneamiento de `paciente`: el `raw` de `/patients/search` tiene ~40 campos. Sólo enviar los 11 básicos (`_id`, `nombre`, `apellido`, `sexo`, `email`, `fechaNacimiento`, `newPatient`, `numeroDeRecord`, `numeroIdentificacion`, `telefono`, `afiliaciones`).
Operations: leer de `paciente.raw.properties` — NO hardcodear todos a `false`, MedicalCore los usa para resolver el estado de admisión.
Headers extra: además de `x-api-key`, este endpoint requiere `enterpriseid: 150575` y `ownerid: 150575`.
Bug 510: casi todos los 510 de este endpoint vienen de: (a) `paciente` con campos extra, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz.
Flujo recomendado
Orden en que deberías llamar a los endpoints.
  1. 1
    GET /doctors?branchId=392 → guardar id y specialtyId del doctor.
  2. 2
    GET /patients/search?cedula={cedula} → guardar id y raw del paciente.
  3. 3
    GET /appointments/availability?doctorId={id}&date={YYYY-MM-DD} → guardar start del slot (en UTC).
  4. 4
    GET /services?specialtyId={specialtyId} → guardar el primer servicio (su _id).
  5. 5
    GET /services/{serviceId}/coverages → guardar el array completo de coverages.
  6. 6
    Mapear specialtyIdconsultationAreaId (ver Mapeo de áreas más abajo).
  7. 7
    Calcular start en formato -04:00 (AST) y end en Z (UTC) — ver regla de timezone.
  8. 8
    POST /appointments con el payload armado a partir de los datos anteriores.
Mapeo de campos
De dónde sale cada valor que tienes que enviar.
CampoOrigen
cita.citaPaciente.pacienteCampo `raw` de `/patients/search` (limpio, sólo 11 campos)
cita.doctorBackend lo resuelve automáticamente por `doctorId`
cita.area._idMapeo specialty → consultation (660, 1816, 622). NO usar la specialty directamente.
cita.subArea._idID del doctor (mismo valor que `doctorId`)
cita.citaPaciente.servicePrimer resultado de `/services?specialtyId=`
cita.citaPaciente.coveragesArray completo de `/services/{id}/coverages`
cita.operationsLeído de `paciente.raw.properties` (NO hardcodear `false`)
cita.startSlot UTC convertido a `-04:00` (AST)
cita.end`start + 20 minutos` en formato `Z` (UTC)

Cuerpo de la petición

Objeto `cita` con todos los datos necesarios. Estructura validada contra MedicalCore RD.

Esquema

CampoTipoInRequeridoDescripción
cita.citaId
integerqueryopcionalSiempre `null` al crear. Enviar como `null` en el JSON.
nullable
cita.doctorIdej. 153625
integerquery
requerido
ID numérico del doctor.
cita.pacienteIdej. 4958123
integerquery
requerido
ID del paciente. Debe ir a nivel raíz, no solo dentro de `citaPaciente`.
cita.pacienteCedulaej. 223-0073480-7
stringqueryopcionalCédula del paciente. Requerida si no envías `citaPaciente.paciente`.
cita.attentionType
stringqueryopcionalTipo de atención.
default: h
cita.type
stringqueryopcional`p` = programada, `d` = directa.
default: p
cita.recurrente
booleanqueryopcional
default: false
cita.doctor._idej. 153625
integerqueryopcionalID del doctor (mismo que `doctorId`).
cita.doctor.nombreej. Pedro
stringqueryopcionalNombre del doctor.
cita.doctor.apellidoej. Rodriguez Gonzalez
stringqueryopcionalApellido del doctor.
cita.doctor.fullNameej. Pedro Rodriguez Gonzalez
stringqueryopcionalNombre completo del doctor.
cita.doctor.universalId
stringqueryopcionalIdentificador universal (puede ir vacío).
default:
cita.doctor.especialidadej. Medicina Interna
stringqueryopcionalNombre de la especialidad (con espacio inicial en el payload real).
cita.area._idej. 660
integerquery
requerido
Área de CONSULTA (mapeada desde specialty). 660, 1816, 622… NO usar la specialty directamente.
cita.area.descriptionej. Consulta de Medicina Interna
stringqueryopcionalDescripción legible del área.
cita.area.type._id
integerqueryopcionalTipo de área. Siempre `1` (Doctores) en este flujo.
default: 1
cita.subArea._idej. 153625
integerqueryopcionalID del doctor (mismo que `doctorId`).
cita.subArea.fullNameej. Pedro Rodriguez Gonzalez
stringqueryopcionalNombre completo del doctor.
cita.startej. 2026-08-05T11:40:00-04:00
string · date-timequery
requerido
Inicio de la cita en `-04:00` (AST), no UTC.
cita.endej. 2026-08-05T16:00:00.000Z
string · date-timequery
requerido
Fin de la cita en `Z` (UTC), `start + 20 minutos`.
cita.status
stringqueryopcionalEstado inicial de la cita.
default: Pendiente por confirmar
cita.citaPaciente.place._id
integerqueryopcionalID del lugar (Médico Express RD).
default: 150575
cita.citaPaciente.service._idej. 2221
integerqueryopcionalID del servicio desde `/services?specialtyId=`.
cita.citaPaciente.coverages[]ej. 75229
integerqueryopcionalArray COMPLETO de coberturas devuelto por `/services/{id}/coverages`.
cita.citaPaciente.paciente
stringqueryopcionalSaneado a 11 campos. Tomado de `raw` de `/patients/search`.
cita.branch._id
integerqueryopcionalID de la sede (392 = San Isidro).
default: 392
cita.servicioAseguradoraej. {"code":"1","description":"Ambulatorio"}
stringqueryopcionalObjeto `{ code, description }`. Default `Ambulatorio`.
cita.doneSaved
booleanqueryopcional
default: false
cita.operationsej. {"priority":false,"fallRisk":false,"wheelChair":false,"vip":false,"companion":false,"overWeight":false,"pregnant":false,"needSupport":false,"disabled":false,"special":false}
stringqueryopcionalObjeto con 10 flags leídos de `paciente.raw.properties`. Ver sección Operations en las notas.
cita.ubicacion
stringqueryopcional`null` por defecto.
nullable
cita.pagadorReferidor
stringqueryopcional`null` por defecto.
nullable
cita.pagador
stringqueryopcionalObjeto pagador (vacío por defecto).
default: {}
cita.referallDoctors
stringqueryopcionalArray de doctores referidores (vacío por defecto).
default: []
cita.ordenMedicaej. {"numero":null,"doctor":{"_id":0,"fullName":null,"especialidad":null}}
stringqueryopcionalEstructura fija con valores null.

Ejemplos

Ejemplo 1
Opción A — mínima con cédula

El backend busca el paciente por la cédula. Sólo lo mínimo indispensable.

opción-a-—-mínima-con-cédula.json·json
{
  "cita": {
    "citaId": null,
    "doctorId": 153625,
    "pacienteId": 4958123,
    "pacienteCedula": "223-0073480-7",
    "start": "2026-08-05T11:40:00-04:00",
    "end": "2026-08-05T16:00:00.000Z",
    "area": {
      "_id": 660,
      "description": "Consulta de Medicina Interna",
      "type": {
        "_id": 1,
        "description": "Doctores"
      }
    },
    "branch": {
      "_id": 392,
      "description": "San Isidro"
    }
  }
}
Ejemplo 2
Opción B — payload completo validado

Estructura probada y validada con MedicalCore RD. Incluye todos los `raw` saneados.

opción-b-—-payload-completo-validado.json·json
{
  "cita": {
    "citaId": null,
    "doctorId": 153625,
    "pacienteId": 4958123,
    "pacienteCedula": "223-0073480-7",
    "attentionType": "h",
    "type": "p",
    "recurrente": false,
    "doctor": {
      "_id": 153625,
      "nombre": "Pedro",
      "apellido": "Rodriguez Gonzalez",
      "fullName": "Pedro Rodriguez Gonzalez",
      "universalId": "",
      "especialidad": " Medicina Interna"
    },
    "area": {
      "_id": 660,
      "description": "Consulta de Medicina Interna",
      "type": {
        "_id": 1,
        "description": "Doctores"
      }
    },
    "subArea": {
      "_id": 153625,
      "fullName": "Pedro Rodriguez Gonzalez"
    },
    "start": "2026-08-05T11:40:00-04:00",
    "end": "2026-08-05T16:00:00.000Z",
    "status": "Pendiente por confirmar",
    "citaPaciente": {
      "place": {
        "_id": 150575,
        "name": "Médico Express RD"
      },
      "service": {
        "_id": 2221,
        "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL",
        "duration": {
          "_id": 1,
          "label": "240 Minutos",
          "value": "m",
          "time": 240
        },
        "baseApiPath": "api/especialidad"
      },
      "pacienteId": 4958123,
      "paciente": {
        "_id": 4958123,
        "nombre": "DANIEL GIOVANNI",
        "apellido": "SANTILLAN PEREZ",
        "sexo": "M",
        "email": "SANTILLANDSP@GMAIL.COM",
        "fechaNacimiento": "1989-05-26T08:00:00.000Z",
        "newPatient": false,
        "numeroDeRecord": "2024-3831",
        "numeroIdentificacion": "223-0073480-7",
        "telefono": [
          {
            "tipo": "Móvil",
            "numero": "829-605-8450"
          }
        ],
        "afiliaciones": []
      },
      "comment": null,
      "internment": false,
      "coverages": [
        {
          "_id": 75229,
          "codCobertura": "00423315A",
          "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL",
          "duration": {
            "_id": 1,
            "label": "20 Minutos",
            "value": "m",
            "time": 20
          },
          "tipo": "Otros",
          "subTipo": " ",
          "dispositivo": false,
          "usePaymentLink": false,
          "servicioAseguradora": {
            "code": "1",
            "description": "Ambulatorio"
          }
        }
      ]
    },
    "branch": {
      "_id": 392,
      "description": "San Isidro"
    },
    "servicioAseguradora": {
      "code": "1",
      "description": "Ambulatorio"
    },
    "doneSaved": false,
    "operations": {
      "priority": false,
      "fallRisk": false,
      "wheelChair": false,
      "vip": false,
      "companion": false,
      "overWeight": false,
      "pregnant": true,
      "needSupport": false,
      "disabled": false,
      "special": false
    },
    "ubicacion": null,
    "pagadorReferidor": null,
    "pagador": {},
    "referallDoctors": [],
    "ordenMedica": {
      "numero": null,
      "doctor": {
        "_id": 0,
        "fullName": null,
        "especialidad": null
      }
    }
  }
}

cURL

request.sh·bash
curl -X POST "https://apimedex.dploy.lol/api/v1/appointments" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $MEDEX_API_KEY" \
  -H "enterpriseid: 150575" \
  -H "ownerid: 150575" \
  -d '{
    "cita": {
      "citaId": null,
      "doctorId": 153625,
      "pacienteId": 4958123,
      "pacienteCedula": "223-0073480-7",
      "attentionType": "h",
      "type": "p",
      "recurrente": false,
      "doctor": { "_id": 153625, "nombre": "Pedro", "apellido": "Rodriguez Gonzalez", "fullName": "Pedro Rodriguez Gonzalez", "universalId": "", "especialidad": " Medicina Interna" },
      "area": { "_id": 660, "description": "Consulta de Medicina Interna", "type": { "_id": 1, "description": "Doctores" } },
      "subArea": { "_id": 153625, "fullName": "Pedro Rodriguez Gonzalez" },
      "start": "2026-08-05T11:40:00-04:00",
      "end":   "2026-08-05T16:00:00.000Z",
      "status": "Pendiente por confirmar",
      "citaPaciente": {
        "place": { "_id": 150575, "name": "Médico Express RD" },
        "service": { "_id": 2221, "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL", "duration": { "_id": 1, "label": "240 Minutos", "value": "m", "time": 240 }, "baseApiPath": "api/especialidad" },
        "pacienteId": 4958123,
        "paciente": { "_id": 4958123, "nombre": "DANIEL GIOVANNI", "apellido": "SANTILLAN PEREZ", "sexo": "M", "email": "SANTILLANDSP@GMAIL.COM", "fechaNacimiento": "1989-05-26T08:00:00.000Z", "newPatient": false, "numeroDeRecord": "2024-3831", "numeroIdentificacion": "223-0073480-7", "telefono": [{"tipo": "Móvil", "numero": "829-605-8450"}], "afiliaciones": [] },
        "comment": null, "internment": false,
        "coverages": [{ "_id": 75229, "codCobertura": "00423315A", "description": "CONSULTA DE SEGUNDA OPINIÓN LOCAL", "duration": { "_id": 1, "label": "20 Minutos", "value": "m", "time": 20 }, "tipo": "Otros", "subTipo": " ", "dispositivo": false, "usePaymentLink": false, "servicioAseguradora": { "code": "1", "description": "Ambulatorio" } }]
      },
      "branch": { "_id": 392, "description": "San Isidro" },
      "servicioAseguradora": { "code": "1", "description": "Ambulatorio" },
      "doneSaved": false,
      "operations": { "priority": false, "fallRisk": false, "wheelChair": false, "vip": false, "companion": false, "overWeight": false, "pregnant": true, "needSupport": false, "disabled": false, "special": false },
      "ubicacion": null, "pagadorReferidor": null, "pagador": {}, "referallDoctors": [],
      "ordenMedica": { "numero": null, "doctor": { "_id": 0, "fullName": null, "especialidad": null } }
    }
  }'

Respuestas
4

StatusDescripción
200Cita creada exitosamente.
400Estructura inválida o paciente no encontrado. Verificar `citaId`, `cita.pacienteId`, `cita.doctorId`, `cita.area._id`.
510MedicalCore no pudo procesar. Casi siempre es: (a) `paciente` con campos extra del `raw`, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz. Ver Troubleshooting 510 abajo.
510Mensaje alternativo: fechas inconsistentes.
200
Cita creada exitosamente.
response-200.json·json
{
  "data": {
    "citaId": "2026-XXXX",
    "_id": 9000000,
    "status": "Pendiente por confirmar"
  }
}
400
Estructura inválida o paciente no encontrado. Verificar `citaId`, `cita.pacienteId`, `cita.doctorId`, `cita.area._id`.
response-400.json·json
{
  "error": "Invalid appointment structure. Must contain cita object with doctorId and pacienteId."
}
510
MedicalCore no pudo procesar. Casi siempre es: (a) `paciente` con campos extra del `raw`, (b) `area._id` de specialty en vez de consulta, (c) `start/end` con timezone mal calculado, (d) falta `cita.pacienteId` a nivel raíz. Ver Troubleshooting 510 abajo.
response-510.json·json
{
  "error": "Cannot read properties of null (reading 'admitionStatus')"
}
510
Mensaje alternativo: fechas inconsistentes.
response-510.json·json
{
  "error": "la fecha fin debe ser mayor a la fecha de inicio"
}