Saltar al contenido
Odontosys para programadores Ayuda para la clínica

Agendar una cita, paso a paso

El recorrido típico de un sitio de reservas. Los ejemplos usan curl; al final está el mismo flujo en JavaScript.

export API=https://api.odontosys.com/publica/v1
export CLAVE="osk_3f9k2m7q_…"

1. Elegí la agenda

curl "$API/agendas" -H "Authorization: Bearer $CLAVE"
{
  "agendas": [
    {
      "id": "12",
      "nombre": "Dr. López",
      "color": "#3366CC",
      "duracionTurnoMin": 30,
      "jornada": {
        "lunes": { "desde": "08:00", "hasta": "20:00", "descanso": { "desde": "12:00", "hasta": "13:00" } },
        "martes": { "desde": "08:00", "hasta": "20:00", "descanso": null },
        "miercoles": null,
        "jueves": { "desde": "08:00", "hasta": "14:00", "descanso": null },
        "viernes": { "desde": "08:00", "hasta": "14:00", "descanso": null },
        "sabado": null,
        "domingo": null
      },
      "ultimoCambio": "2026-09-21T13:59:02-03:00"
    }
  ]
}

jornada es el horario habitual de cada día (null = no trabaja). Los días puntuales con otro horario —un feriado, una tarde libre— no están acá: los aplica disponibilidad. Para mostrar horarios, usá siempre disponibilidad.

ultimoCambio cambia cada vez que alguien toca algo en esa agenda. Si lo guardás, podés evitar volver a pedir las citas cuando no cambió.

2. Pedí los horarios libres

curl "$API/agendas/12/disponibilidad?desde=2026-10-05&hasta=2026-10-09&duracionMin=30" \
  -H "Authorization: Bearer $CLAVE"
{
  "agenda": "12",
  "duracionMin": 30,
  "dias": [
    { "fecha": "2026-10-05", "trabaja": true,
      "huecos": [ { "desde": "08:00", "hasta": "09:30" }, { "desde": "10:00", "hasta": "12:00" } ] },
    { "fecha": "2026-10-07", "trabaja": false, "huecos": [] }
  ]
}

3. Identificá al paciente (opcional)

Si tu sistema sabe el celular, el correo o el número de ficha, podés buscar si ya es paciente de la clínica (permiso pacientes:buscar):

curl "$API/pacientes/buscar?celular=099123456" -H "Authorization: Bearer $CLAVE"
{ "pacientes": [ { "id": "678", "nombre": "Juan Gómez" } ] }

4. Creá la cita

curl -X POST "$API/citas" \
  -H "Authorization: Bearer $CLAVE" \
  -H "Content-Type: application/json" \
  -d '{
    "idAgenda": "12",
    "fecha": "2026-10-05",
    "hora": "08:30",
    "duracionMin": 30,
    "paciente": { "id": "678" },
    "nota": "Primera consulta (reservó por la web)",
    "referenciaExterna": "reserva-8812"
  }'

Responde 201 con la cita y la cabecera Location: /publica/v1/citas/17680900:

{
  "id": "17680900", "idAgenda": "12", "fecha": "2026-10-05", "hora": "08:30", "duracionMin": 30,
  "tipo": "paciente", "paciente": { "id": "678", "nombre": "Juan Gómez" }, "nombre": "Juan Gómez",
  "nota": "Primera consulta (reservó por la web)", "estado": "enEspera", "confirmada": false,
  "creadaPorApi": true, "referenciaExterna": "reserva-8812",
  "creada": "2026-09-21T14:03:11-03:00", "modificada": "2026-09-21T14:03:11-03:00"
}

Paciente con ficha o sin ficha:

paciente Queda en la agenda Recordatorios
{ "id": "678" } A nombre del paciente, vinculada a su ficha , los que tenga configurados la clínica
{ "nombre": "Ana Ruiz", "telefono": "099555444" } Con ese nombre; el teléfono va en la nota No

En una cita sin ficha, la nota que devuelve la API empieza con el teléfono: "Tel: 099555444 · Primera consulta".

Los topes: nota 200 caracteres, nombre 100, telefono 30, referenciaExterna 64. La duración va de 5 a 480 minutos y la cita tiene que terminar a más tardar a las 23:59.

Lo que puede salir mal, en el orden en que se verifica:

Respuesta Qué pasó Qué hacer
400 solicitud_invalida La forma del pedido. detalle.campo dice cuál Corregir el pedido
404 no_encontrado La agenda no existe, está inactiva o la clave no la ve Volver a pedir GET /agendas
422 paciente_invalido Ese paciente no existe o está inactivo Agendar como alguien sin ficha
422 fecha_fuera_de_rango La fecha es anterior a hoy o pasa de hoy + 365 días
422 duracion_invalida Menos de 5 o más de 480 minutos, o termina después de las 23:59
422 agenda_no_trabaja Ese día la agenda no atiende Ofrecer otro día
422 fuera_de_horario La cita no entra entera en la jornada, o pisa el descanso Ofrecer otro horario
409 horario_ocupado Alguien ocupó ese horario desde que pediste la disponibilidad Volver a pedir disponibilidad y ofrecer otro

El 409 horario_ocupado es normal: entre que el paciente mira los horarios y confirma, la clínica o otro paciente pueden tomar el mismo. Tu sitio tiene que manejarlo con un mensaje amable, no con un error.

5. Reintentar sin duplicar: referenciaExterna

Si el POST se corta (un timeout, la red), no sabés si la cita se creó. Mandá siempre una referenciaExterna —el id de la reserva en tu sistema— y reintentá con la misma:

La referencia es única por clínica, entre todas sus claves.

6. Consultar y eliminar

Con citas:crear ves las citas que creó la integración:

curl "$API/citas?desde=2026-10-01&hasta=2026-10-31" -H "Authorization: Bearer $CLAVE"
curl "$API/citas/17680900" -H "Authorization: Bearer $CLAVE"

Para cancelar una reserva, eliminá la cita:

curl -X DELETE "$API/citas/17680900" -H "Authorization: Bearer $CLAVE"

Responde 204 sin cuerpo. Sólo se pueden eliminar citas que creó la API de la clínica (con cualquiera de sus claves), de hoy en adelante, y que la clínica todavía no haya marcado (asistencia o llegada a la sala de espera):

Respuesta Qué pasó
403 cita_no_creada_por_api La cita la cargó la clínica: la API no la puede eliminar
409 cita_no_eliminable Ya pasó, o la clínica ya marcó la asistencia o la llegada
410 cita_eliminada Ya estaba eliminada
410 cita_reprogramada La clínica la movió: ver abajo

Eliminar es definitivo, igual que en Odontosys: la cita sale de la agenda y queda registrada en el Historial de actividad de la clínica como hecha por tu clave.

Si la clínica la mueve

Cuando la clínica cambia el día, la hora, la agenda o el paciente de una cita, Odontosys la elimina y la vuelve a crear con otro id. Para tu integración:

Si tu sistema necesita seguirla, buscala con GET /citas en el rango de fechas (necesita citas:leer) y avisale al paciente que la clínica la cambió. En esta versión no hay avisos automáticos (webhooks) ni ids que sobrevivan al reagendamiento.

El mismo flujo en JavaScript

const API = "https://api.odontosys.com/publica/v1";
const CLAVE = process.env.ODONTOSYS_CLAVE; // nunca en el código ni en el navegador

async function api(metodo, ruta, cuerpo) {
  const r = await fetch(API + ruta, {
    method: metodo,
    headers: { Authorization: `Bearer ${CLAVE}`, "Content-Type": "application/json" },
    body: cuerpo ? JSON.stringify(cuerpo) : undefined,
  });
  if (r.status === 204) return null;
  const json = await r.json();
  if (!r.ok) {
    const e = new Error(json.error.mensaje);
    Object.assign(e, { status: r.status, codigo: json.error.codigo, idSolicitud: json.error.idSolicitud });
    throw e;
  }
  return json;
}

// Horarios libres de la semana
const { dias } = await api("GET", "/agendas/12/disponibilidad?desde=2026-10-05&hasta=2026-10-09");

// Reservar, reintentando sin duplicar
try {
  const cita = await api("POST", "/citas", {
    idAgenda: "12", fecha: "2026-10-05", hora: "08:30", duracionMin: 30,
    paciente: { nombre: "Ana Ruiz", telefono: "099555444" },
    referenciaExterna: "reserva-8813",
  });
  console.log("Reservada:", cita.id);
} catch (e) {
  if (e.codigo === "horario_ocupado") {
    // mostrar otros horarios
  } else {
    throw e;
  }
}

Para los 429 y 503, ver Límites y reintentos.