← API para desarrolladores

API pública v1 — guía para integradores

Estado: vigente desde el 17/09/2026. Contrato vivo en /api/v1/openapi.json. Página pública: /desarrolladores.

Esta guía es para quien integra su sistema —historia clínica, gestión de clínica, laboratorio, farmacia, software de obra social, un turnero propio— con la agenda de un consultorio que usa Mi Agenda Profesional. Cubre las dos direcciones: ellos nos llaman (API) y nosotros les avisamos (webhooks).


1. En dos minutos

# 1. El dueño del consultorio crea una clave en Configuración → Integraciones → API y webhooks.
# 2. Probala:
curl https://miagendaprofesional.com/api/v1/me \
  -H "Authorization: Bearer map_live_…"

# 3. Mirá qué hay:
curl "https://miagendaprofesional.com/api/v1/locations" -H "Authorization: Bearer map_live_…"
curl "https://miagendaprofesional.com/api/v1/services"  -H "Authorization: Bearer map_live_…"

# 4. Pedí horarios libres y reservá:
curl "https://miagendaprofesional.com/api/v1/availability?date=2026-10-15&location_id=…&service_id=…" \
  -H "Authorization: Bearer map_live_…"

curl -X POST https://miagendaprofesional.com/api/v1/appointments \
  -H "Authorization: Bearer map_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f1c…" \
  -d '{
    "patient": { "full_name": "Juan Pérez", "phone": "+5491155551234", "dni": "30111222" },
    "start": "2026-10-15T14:00:00-03:00",
    "location_id": "…", "service_id": "…",
    "external_ref": "tu-id-123"
  }'

La respuesta del POST trae el turno, el link de pago si el consultorio cobra seña, y el link de Meet si la consulta es online. El paciente recibe el mismo WhatsApp y mail que si hubiera reservado por el sitio (salvo que mandes "notify": false).

HTTP/1.1 201 Created
X-Api-Version: 1
X-RateLimit-Limit: 60

{
  "data": {
    "id": "9c1e6b2a-…", "status": "confirmed",
    "start_at": "2026-10-15T17:00:00Z", "end_at": "2026-10-15T17:30:00Z",
    "location_id": "…", "provider_id": "…", "service_id": "…", "service_name": "Consulta",
    "modality": "presencial", "booking_source": "api_v1",
    "patient": { "id": "…", "full_name": "Juan Pérez", "phone": "+5491155551234", "email": null, "dni": "30111222" },
    "patient_note": null, "appointment_reason": null, "cancellation_reason": null,
    "payment_link": null, "meeting_url": null, "reschedule_count": 0,
    "created_at": "2026-09-19T14:02:11Z", "updated_at": "2026-09-19T14:02:11Z"
  },
  "payment": { "link": null, "charged_minor_units": null },
  "meeting_url": null,
  "sandbox": false
}

Si el consultorio cobra seña, payment.link trae el checkout y payment.charged_minor_units cuánto pide (en centavos), y el turno nace en awaiting_payment. Es el mismo número que le dice el WhatsApp al paciente.


2. Autenticación

  • Clave de API en Authorization: Bearer <clave>. Ninguna otra cabecera vale.
  • Formato: map_live_ + 48 hex (producción) o map_test_ + 48 hex (prueba). Las claves viejas rx_live_* / rx_sand_* siguen valiendo.
  • La clave se muestra una sola vez al crearla. Guardamos el hash SHA-256; si se pierde, se revoca y se crea otra. Revocar es instantáneo.
  • Cada clave pertenece a un consultorio: todo lo que lee y escribe es de ese consultorio. No hay claves multi-consultorio; una clínica con tres sedes es un consultorio con tres sedes.
  • Quién la crea: el dueño de la cuenta, desde Configuración → Integraciones. La recepción no puede.
  • Se puede editar el nombre, los alcances, el webhook (URL, secreto y eventos) y el tope por minuto, sin cambiar la clave: tu integración sigue andando con la que ya tenés cargada. Lo que no se edita es la clave, su prefijo ni el modo de prueba: van adentro de la clave que ya está en tu sistema, así que cambiarlos dejaría una clave que dice una cosa y hace otra. Para eso se revoca y se emite otra.

Alcances

Cada clave declara qué puede hacer. Pedí el mínimo.

AlcancePermite
catalog:readProfesionales, prestaciones, sedes, /me, /webhooks/ping.
availability:readHorarios libres.
appointments:readListar y leer turnos.
appointments:writeReservar, confirmar, reprogramar, cancelar.
patients:readBuscar y leer fichas (contacto y cobertura; nunca historia clínica).
patients:writeCrear y actualizar fichas.
prescriptions:readLeer recetas, PDF y FHIR.
prescriptions:writeEmitir y anular recetas.
dispense:writeRegistrar dispensas (farmacias).
*Todo, incluido lo que se agregue después.

Sin el alcance: 403 con code: "insufficient_scope" y details.required.

Topes

  • Por clave: rate_limit_per_minute (default 60, máximo 600), contado en la base, así que vale entre instancias. Al superarlo: 429, Retry-After en segundos, X-RateLimit-Limit en toda respuesta.
  • Por IP: 300 pedidos por minuto a /api/v1 y a /api/rx/v1, para frenar a quien prueba claves al azar.
  • Si necesitás más de 600 por minuto, escribinos: es una conversación, no un formulario.

Claves de prueba (map_test_)

Operan sobre el mismo consultorio y los mismos datos: no hay un entorno aparte. Lo que cambia:

  • No se avisa al paciente (ni WhatsApp ni mail) por lo que crea o cambia.
  • Lo que crea queda marcado (booking_source = api_sandbox, identity_source = api_sandbox) para poder borrarlo después.
  • Los webhooks llevan "sandbox": true.

Probá contra un consultorio de prueba, no contra uno con pacientes reales.


3. Convenciones

  • Base: https://miagendaprofesional.com/api. Versión en la URL (/v1/…) y en la cabecera de respuesta X-Api-Version.
  • JSON en los dos sentidos. snake_case. Fechas en ISO 8601 con zona (2026-10-15T14:00:00-03:00); las respuestas vienen en UTC (…Z).
  • Ids son UUID. Los códigos de receta son MAP-XXXX-XXXX-XXXX.
  • Listas: { "data": [...], "next_offset": 100 | null }. limit hasta 100.
  • Errores: siempre { "error": "<texto>", "code": "<código>", "details"? }. Programá contra code; el texto puede cambiar.
codeHTTPCuándo
missing_api_key401Sin Authorization: Bearer.
invalid_api_key401Clave inexistente o revocada.
tenant_suspended403La cuenta del consultorio está suspendida.
insufficient_scope403Falta el alcance.
plan_required402El plan del consultorio no lo permite (p. ej. cupo de turnos del mes).
forbidden403La acción no está permitida en ese estado: agenda cerrada, farmacia no habilitada, quien retira no puede, matrícula del prescriptor que REFEPS rechaza. No es de plan ni de alcance.
rate_limited429Tope por minuto. Mirá Retry-After.
validation_error400Cuerpo o parámetros inválidos; details dice qué.
not_found404El recurso no existe en este consultorio.
conflict409Horario ocupado, estado que no admite la acción, duplicado.
idempotency_key_reused422Misma Idempotency-Key con otro cuerpo.
service_unavailable503Reintentá en unos segundos (Retry-After). También cuando no pudimos verificar la clave: no es que la clave sea inválida.
internal_error500Nuestro. Reintentá; si persiste, avisá.

Tres errores, tal como llegan:

HTTP/1.1 400 Bad Request
{ "error": "Cuerpo inválido.", "code": "validation_error",
  "details": [ { "path": "patient.phone", "message": "Invalid" }, { "path": "start", "message": "Required" } ] }
HTTP/1.1 403 Forbidden
{ "error": "La clave no tiene el alcance appointments:write.", "code": "insufficient_scope",
  "details": { "required": "appointments:write", "granted": ["catalog:read", "availability:read"] } }
HTTP/1.1 409 Conflict
{ "error": "Ese horario no está disponible.", "code": "conflict",
  "details": { "reason": "fully_booked", "alternatives": ["2026-10-15T17:30:00Z", "2026-10-15T18:00:00Z"] } }

Y un 429, con lo único que hace falta mirar en la cabecera:

HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

Todo rechazo —clave inválida, alcance, suspensión, tope— queda registrado de nuestro lado, con el comienzo de la clave y la IP. Si un integrador reporta «me contesta 401», podemos ver desde cuándo y desde dónde.

Idempotencia

Todo POST que crea algo —turnos, pacientes, recetas, dispensas, ping— acepta Idempotency-Key (hasta 128 caracteres; usá un UUID por pedido). Reintentar con la misma clave y el mismo cuerpo devuelve la respuesta original con Idempotent-Replayed: true, sin crear nada. Con otro cuerpo, 422. Dura 24 horas. Los 5xx no se memorizan: reintentalos con la misma clave.

La clave se reserva antes de ejecutar: si reintentás mientras el primer pedido todavía está corriendo, recibís 409 conflict con Retry-After: 2. Esperá y volvé a mandar la misma clave; vas a recibir la respuesta del primero.

Usala siempre en POST /appointments y en POST /rx/v1/prescriptions: un turno duplicado molesta; una receta duplicada es un documento legal de más.


4. Recursos

GET /v1/me

Quién sos: clave, alcances, sandbox, tope, webhook, y el consultorio.

{
  "client": { "id": "…", "name": "Sistema de la clínica", "key_prefix": "map_live_a1b2",
              "scopes": ["catalog:read", "appointments:write"], "sandbox": false,
              "rate_limit_per_minute": 60, "webhook_url": "https://…", "webhook_events": null },
  "tenant": { "id": "…", "name": "Consultorio Demo", "slug": "consultorio-demo" }
}

El secreto del webhook no viaja: lo tenés vos, lo tenemos nosotros, y no vuelve a salir por ningún lado.

Catálogo (catalog:read)

  • GET /v1/providers — profesionales activos (?include_inactive=true para todos).
  • GET /v1/services — prestaciones: duración, precio en unidades menores (price_minor_units, ARS → centavos), modalidad.
  • GET /v1/locations — sedes con timezone y business_hours.

Disponibilidad (availability:read)

GET /v1/availability?date=YYYY-MM-DD&location_id=…[&service_id=…][&provider_id=…][&duration=…]

Devuelve slots (inicios en ISO UTC) y reason cuando no hay ninguno (feriado, sede cerrada, profesional sin horario ese día…). Es la misma función que ve el paciente en el micrositio: horario de la sede, bloqueos, feriados, turnos existentes, exclusividades y el Google Calendar del profesional. Sin duration, manda la de la prestación; sin prestación, la de la sede.

{ "data": { "date": "2026-10-15", "duration_minutes": 30,
            "slots": ["2026-10-15T12:00:00Z", "2026-10-15T12:30:00Z", "2026-10-15T13:00:00Z"],
            "reason": null } }

Un día sin nada contesta "slots": [] y el motivo en reason: fully_booked (todo ocupado), no_schedule (nadie atiende ese día), otra_sede (el profesional atiende en otra sede ese día) o servicio_no_ese_dia (la prestación tiene horario propio y no incluye ese día). No es un error, es la respuesta.

Turnos

  • GET /v1/appointments?from&to&status&patient_id&provider_id&location_id&limit&offset (appointments:read). Ventana por start_at; default: hace 30 días a dentro de 90; máximo un año. status=live = en pie.
  • GET /v1/appointments/{id}.
  • POST /v1/appointments (appointments:write) — reservar. Ver §1. Reglas:
    • Pasa por el mismo camino que el sitio y el bot: cupo del plan, agenda cerrada, prestación pausada, coherencia sede/profesional, seña anti-ausencia, lista de espera. Lo que ahí se niega, acá también.
    • Sin sobreturnos ni fechas pasadas. Eso es del panel, con una persona decidiendo.
    • patient puede ser { "id": "…" } (existente) o { "full_name", "phone"?, "email"?, "dni"? }: se busca por teléfono/email y si no existe se crea.
    • external_ref (tu id) queda en las notas internas del turno.
  • POST /v1/appointments/{id}/confirm — lo marca confirmado y anota que confirmó el paciente. No manda mensajes.
  • POST /v1/appointments/{id}/reschedule { "start", "provider_id"?, "notify"? } — mueve conservando duración, sede, prestación y paciente. El destino tiene que estar libre; si no, 409 con details.alternatives. Respeta el tope de reprogramaciones del consultorio.
  • POST /v1/appointments/{id}/cancel { "reason"?, "notify"? } — mismos efectos que el botón del panel: aviso al paciente y al profesional, baja en Google Calendar, oferta del hueco a la lista de espera.

Estados: pending, confirmed, awaiting_payment, reschedule_requested (en pie); canceled, completed, no_show (terminales).

Una lista, con la paginación:

GET /v1/appointments?from=2026-10-01&to=2026-10-31&status=live&limit=2

{ "data": [ { "id": "…", "status": "confirmed", "start_at": "2026-10-02T13:00:00Z", "…": "…" },
            { "id": "…", "status": "pending",   "start_at": "2026-10-02T14:00:00Z", "…": "…" } ],
  "next_offset": 2 }

Mientras next_offset no sea null, hay más: repetí con offset=<next_offset>.

Reprogramar y cancelar:

curl -X POST …/api/v1/appointments/9c1e6b2a-…/reschedule \
  -H "Authorization: Bearer map_live_…" -H "Content-Type: application/json" \
  -d '{ "start": "2026-10-16T10:00:00-03:00" }'
# 200 → { "data": { …el turno ya movido, "reschedule_count": 1 }, "notified": true }
# 409 → { "code": "conflict", "details": { "reason": "…", "alternatives": ["…"] } }

curl -X POST …/api/v1/appointments/9c1e6b2a-…/cancel \
  -H "Authorization: Bearer map_live_…" -H "Content-Type: application/json" \
  -d '{ "reason": "El paciente avisó que no puede", "notify": true }'
# 200 → { "data": { …, "status": "canceled", "cancellation_reason": "El paciente avisó que no puede" }, "notified": true }

Pacientes

  • GET /v1/patients?q&dni&phone&email&limit&offset (patients:read).
  • GET /v1/patients/{id}.
  • POST /v1/patients (patients:write) — si ya existe uno con ese DNI (o, sin DNI, ese teléfono) devuelve 200 con created: false. Mandá al menos phone, email o dni.
  • PATCH /v1/patients/{id} — sólo los campos que mandás; null borra. No toca opt_out (la baja de mensajes la decide el paciente).

La ficha de la API es contacto y cobertura. No expone notas, historia clínica, formularios ni resúmenes. Eso no sale por acá y no va a salir.

POST /v1/patients
{ "full_name": "Juan Pérez", "phone": "+5491155551234", "dni": "30111222", "birth_date": "1990-05-01", "sex": "M",
  "insurance_provider": "OSDE", "insurance_number": "61234567801" }

HTTP/1.1 201 Created
{ "data": { "id": "…", "full_name": "Juan Pérez", "given_name": "Juan", "family_name": "Pérez",
            "phone": "+5491155551234", "email": null, "dni": "30111222", "birth_date": "1990-05-01", "sex": "M",
            "insurance_provider": "OSDE", "insurance_number": "61234567801", "insurance_plan": null,
            "opt_out": false, "last_visit_at": null, "created_at": "2026-09-19T14:02:11Z" },
  "created": true }

Si ya existía, es 200 con "created": false y la ficha que ya estaba: no se crea una segunda.

Recetas (/api/rx/v1/…)

Receta electrónica conforme a la Ley 27.553 y Res. 638/2026. Plataforma inscripta en ReNaPDiS (N° 297).

  • POST /rx/v1/prescriptions (prescriptions:write) — dos formas: inline (mandás prescriber y patient con sus datos; para software de terceros, no hace falta que existan en la plataforma) o captiva (providerId/patientId). Obligatorios por el conjunto mínimo de datos: prescriber.address, y en cada item presentation y pharmaForm. Controlados → 422. La respuesta trae code, verify_url (el QR), pdf_url y fhir_url.
  • GET /rx/v1/prescriptions?patientDni&status&limit, GET …/{code}, DELETE …/{code} (anular, { "reason"? }).
  • GET …/{code}/pdf — el PDF. GET …/{code}/fhir — Bundle FHIR R4 (MedicationRequest + Patient + Practitioner, application/fhir+json).
  • POST …/{code}/dispense (dispense:write) — para farmacias: pharmacyName, pharmacyCuit, pharmacistName, pickupDocumentNumber (art. 4), authorizationCode si retira otra persona (art. 3), items para dispensa parcial. La farmacia se contrasta contra REFES cuando hay credenciales; sólo un no afirmativo bloquea.

Detalle del cuerpo inline en recetas-api-v1.md y en el OpenAPI (PrescriptionInlineInput).

La matrícula se contrasta contra REFEPS. En la forma inline, si mandás prescriber.cuil o prescriber.refepsCode, verificamos la matrícula contra el registro federal antes de emitir. REFEPS identifica a la persona, no busca por número de matrícula: sin uno de esos dos datos no se puede preguntar. Sólo un no afirmativo —«no figura» o «no habilitada»— rechaza, con 403 y code: "forbidden"; no poder consultar (sin credenciales, servicio caído) deja la receta salir con refeps_status: "unverified" y el motivo en el snapshot del prescriptor.

Ley 27.675. Si la receta consigna una patología protegida (VIH, hepatitis virales, tuberculosis, ITS), GET …/{code} y …/fhir devuelven el diagnóstico reservado, igual que el PDF: diagnosis lleva el texto de reserva, cie10_description va en null, cie10_code se conserva —es lo que la obra social necesita— y diagnosis_redacted: true lo señala para que no lo leas como un dato truncado.

{ "prescription": { "code": "MAP-1A2B-3C4D-5E6F", "status": "issued",
                    "diagnosis": "CIE-10 B20 — reservado (Ley 27.675)", "cie10_code": "B20", "cie10_description": null,
                    "diagnosis_redacted": true,
                    "diagnosis_redaction_note": "Diagnóstico reservado por la Ley 27.675 de confidencialidad. Consta en la historia clínica.",
                    "…": "…" },
  "sandbox": false }

5. Webhooks (nosotros te avisamos)

Configurá en la clave una URL HTTPS pública y un secreto (16 a 128 caracteres, lo elegís vos). Opcionalmente, qué eventos querés; sin filtro, todos.

Eventos

EventoCuándo
pingPrueba: POST /v1/webhooks/ping con la clave (alcance catalog:read). El consultorio ve en su panel si llegó.
appointment.createdSe reservó un turno, por cualquier canal (panel, sitio, bot de WhatsApp, API).
appointment.confirmedPasó a confirmado.
appointment.rescheduledCambió el inicio (data.previous_start_at).
appointment.cancelledSe canceló (data.previous_status, data.cancellation_reason).
appointment.completed / appointment.no_showSe marcó atendido / ausente.
appointment.updatedOtro cambio de estado, o cambió el profesional.
patient.createdSe creó una ficha.
rx.issued / rx.cancelled / rx.dispensed / rx.expiredRecetas. data trae code y prescriptionId; el contenido lo pedís por GET …/{code} con tu alcance.

Entrega

POST https://tu-sistema.com.ar/hooks/miagenda
Content-Type: application/json
User-Agent: miagendaprofesional-webhooks/1.0
X-Webhook-Id: 3d2f…            ← id de la entrega; se repite en cada reintento
X-Webhook-Event: appointment.cancelled
X-Webhook-Timestamp: 1760000000 ← segundos Unix
X-Webhook-Attempt: 1
X-Webhook-Signature: sha256=…

{
  "id": "3d2f…",
  "event": "appointment.cancelled",
  "created_at": "2026-10-15T17:03:11Z",
  "sandbox": false,
  "data": {
    "appointment_id": "…", "status": "canceled", "previous_status": "confirmed",
    "start_at": "…", "end_at": "…", "patient_id": "…", "provider_id": "…",
    "service_id": "…", "location_id": "…", "modality": "presencial",
    "booking_source": "bot_whatsapp", "cancellation_reason": "…"
  }
}

Los eventos son flacos: ids y estado. Si necesitás el recurso entero, pedilo por la API con el id. Así un evento no arrastra datos del paciente por un canal que no los pidió.

Verificar la firma (obligatorio)

const crypto = require("crypto");
function verificar(req, rawBody, secret) {
  const ts = req.headers["x-webhook-timestamp"];
  const firma = req.headers["x-webhook-signature"]; // "sha256=…"
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5 min: replay
  const esperada = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return firma.length === esperada.length && crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
}
import hmac, hashlib, time

def verificar(headers, raw_body: bytes, secret: str) -> bool:
    ts = headers.get("X-Webhook-Timestamp", "")
    firma = headers.get("X-Webhook-Signature", "")
    if abs(time.time() - float(ts or 0)) > 300:
        return False
    esperada = "sha256=" + hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(firma, esperada)
function verificar(array $headers, string $rawBody, string $secret): bool {
    $ts = $headers['X-Webhook-Timestamp'] ?? '';
    $firma = $headers['X-Webhook-Signature'] ?? '';
    if (abs(time() - (int) $ts) > 300) return false;
    $esperada = 'sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret);
    return hash_equals($esperada, $firma);
}

Las tres hacen lo mismo: HMAC-SHA256(secreto, "<timestamp>.<cuerpo crudo>") en hexadecimal, con sha256= adelante, comparado en tiempo constante.

Firmá sobre el cuerpo crudo, no sobre el JSON re-serializado. Los eventos rx.* llevan además X-Rx-Event / X-Rx-Timestamp / X-Rx-Signature con la misma firma, para quien ya las leía.

Reintentos y deduplicación

  • Contestá 2xx en menos de 10 segundos. Cualquier otra cosa (timeout, 4xx, 5xx, redirección) cuenta como fallo.
  • Reintentamos con espera creciente: 1, 5, 30, 120 y 720 minutos. Seis intentos en total (~14 h). Después, la entrega queda como agotada y se ve en el panel del consultorio.
  • El consultorio ve cada intento —cuándo, qué código devolviste y cuánto tardó— y puede pedir un intento más sobre una agotada, una vez ya arreglado tu endpoint. Es un intento por click, no seis: si vuelve a fallar, vuelve a quedar agotada. Si necesitás que te reenvíen algo, pedíselo al consultorio con el X-Webhook-Id.
  • El X-Webhook-Id se repite en cada reintento: guardalo y descartá lo que ya procesaste. Podés recibir un evento dos veces; nunca deberías procesarlo dos veces.
  • El orden no está garantizado entre eventos distintos. Mirá created_at.
  • Probá con POST /v1/webhooks/ping antes de esperar el primer evento real.

Si el consultorio se suspende

Una cuenta suspendida no manda webhooks, del mismo modo que la API le contesta 403 tenant_suspended a sus lecturas. Lo que estaba en cola al momento de la suspensión se cierra sin entregar, con ese motivo, y se ve en el panel. Cuando la cuenta se reactiva, los eventos nuevos salen normalmente; los del período suspendido no se reenvían: pedí el estado actual por la API.

Seguridad de la URL

Sólo HTTPS a un host público. Rechazamos localhost, IPs privadas, link-local, la metadata de la nube, .local/.internal y usuario:clave en la URL, al guardar y al entregar (resolviendo DNS).


6. Qué NO hace la API, y por qué

  • No expone historia clínica (notas, evoluciones, formularios, resúmenes de IA, recetas de otros consultorios). Es dato sensible con régimen propio (Ley 25.326, Ley 26.529, Ley 27.675). Si un caso de uso lo necesita, se conversa con el consultorio y el paciente, no se abre un alcance.
  • No configura el consultorio (horarios, profesionales, prestaciones, precios, integraciones, plan). Eso se hace a mano en Configuración, con sus propios gates.
  • No manda mensajes de texto libre a pacientes. Los avisos salen de las plantillas del consultorio, por los canales que él configuró.
  • No hace sobreturnos ni turnos en el pasado. Es el panel, con una persona.
  • No hay OAuth de usuario final. La clave es del consultorio, la crea su dueño. Si tu producto necesita que muchos consultorios te autoricen con un click, escribinos: es el siguiente paso y depende de que exista la demanda.

7. Versionado y cambios

  • v1 es estable. Agregar campos, endpoints, eventos o valores de enum no es un cambio de versión: tolerá campos desconocidos.
  • Sacar o renombrar algo, o cambiar semántica, es v2, con al menos seis meses de convivencia y aviso al mail de contacto de cada clave activa.
  • Los cambios se anuncian en esta guía, con fecha.

19/09/2026. Aditivo, sin cambio de versión: diagnosis_redacted y diagnosis_redaction_note en las recetas; prescriber.cuil en la emisión inline y 403 forbidden cuando REFEPS rechaza la matrícula; 402 plan_required al emitir sin el plan; una cuenta suspendida deja de recibir webhooks; los rechazos quedan registrados.

8. Contacto

soporte@miagendaprofesional.com. Para una integración de producto (no un consultorio suelto), decinos qué sistema es, cuántos consultorios y qué necesitan leer y escribir: el alcance se acuerda antes que el código.