Doctia
Historia clínicaPágina webPrecioEspecialidadesIngresarCrear mi cuenta gratis

API y webhooks de Doctia

API REST v1 · JSON · https://doctia.co/api/v1

  1. Qué ofrece la API
  2. Cómo obtener una clave
  3. Autenticación
  4. Permisos
  5. Límites
  6. Paginación
  7. Fechas y zona horaria
  8. Errores
  9. Endpoints
  10. Webhooks
  11. Exportación completa
  12. Guías de integración
  13. Referencia OpenAPI

Qué ofrece la API

La API de Doctia conecta el consultorio con sus otros sistemas: lee y escribe pacientes y citas, lee las historias clínicas y evoluciones con sus diagnósticos, los paquetes de sesiones y el catálogo de profesionales, servicios y sedes. Los webhooks avisan en el momento en que algo pasa: una cita nueva, una cita atendida o cancelada, una historia firmada, un paciente nuevo o una sesión firmada.

Sirve para la contabilidad, la facturación electrónica, los tableros de BI, el CRM y la logística de atención domiciliaria. Y con la exportación completa, los datos de su consultorio son suyos: los descarga cuando quiera, en CSV y JSON, también si la suscripción está vencida.

Cómo obtener una clave

  • En el panel de Doctia, abra RIPS y API › API y webhooks.
  • Solo el titular de la cuenta (rol propietario) crea y revoca claves, en los planes PRO y CENTRO.
  • Póngale un nombre (por ejemplo «Facturación») y marque solo los permisos que esa integración necesita.
  • La clave completa (dk_live_…) se muestra una sola vez: cópiela y guárdela en el gestor de secretos de su servidor. Doctia guarda solo su huella, no la clave.
  • Si una clave se expone, revóquela en el mismo lugar: deja de funcionar en el acto.

Autenticación

Cada solicitud lleva la clave en el encabezado Authorization:

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/cuenta"

La API es de servidor a servidor: nunca ponga la clave en una página web ni en una aplicación móvil. Cada clave ve solo su cuenta; un id de otra cuenta responde 404, igual que uno que no existe. Cada llamada queda en la bitácora de la cuenta, con la clave que la hizo.

Permisos

Una operación sin el permiso responde 403 PERMISO_INSUFICIENTE.

PermisoQué permite
pacientes:leerLista y datos de los pacientes.
pacientes:escribirRegistrar pacientes y cambiar sus datos.
citas:leerAgenda: citas por fecha, profesional y estado.
citas:escribirAgendar, reprogramar, cambiar el estado y cancelar, con las mismas reglas de la agenda.
historias:leerHistorias y evoluciones (firmadas y borradores) con sus diagnósticos.
historias:escribirCrear el borrador de una evolución. La firma siempre la hace el profesional en el panel.
paquetes:leerPaquetes de sesiones con el estado y la firma de cada sesión.
catalogo:leerProfesionales, servicios y sedes.
exportarDescargar un ZIP con todos los datos de la cuenta (incluidas las historias).

Límites

Hasta 120 solicitudes por minuto por clave. Cada respuesta trae X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (segundos epoch en que se reinicia la ventana). Al pasarse, la respuesta es 429 con Retry-After. El cuerpo de una solicitud puede pesar hasta 1 MB.

Paginación

Las listas de pacientes, citas, historias y paquetes van por cursor, ordenadas por id: limite (de 1 a 200; 50 si no lo envía) y despues_de (el id del último elemento recibido). Para la página siguiente envíe despues_de con el valor de paginacion.siguiente.

{
  "data": [ { "id": 9123, "estado": "confirmada", "fecha": "2026-10-20", … } ],
  "paginacion": { "limite": 50, "hay_mas": true, "siguiente": 9123 }
}

Fechas y zona horaria

  • Los instantes (creada_en, actualizada_en, firmada_en…) van en ISO 8601 en UTC, con «Z».
  • La agenda (fecha, hora_inicio, hora_fin) va en hora de Colombia, sin zona, igual que en el panel. inicio y fin traen el mismo momento con el desfase de Colombia (-05:00, sin horario de verano).
  • Los filtros desde y hasta son fechas de Colombia (AAAA-MM-DD); actualizados_desde y actualizadas_desde son instantes ISO 8601.
{
  "id": 9123,
  "estado": "confirmada",
  "fecha": "2026-10-20",
  "hora_inicio": "09:00",
  "hora_fin": "09:30",
  "inicio": "2026-10-20T09:00:00-05:00",
  "fin": "2026-10-20T09:30:00-05:00",
  "paciente": { "id": 6538, "nombre": "Laura Gómez", "telefono": "3001234567", "correo": null },
  "profesional": { "id": 12, "nombre": "Ana Ruiz" },
  "servicio": { "id": 40, "nombre": "Consulta" },
  "sede": null,
  "creada_en": "2026-10-08T19:17:39Z",
  "actualizada_en": "2026-10-08T19:20:02Z"
}

Errores

Todo error tiene la misma forma; programe contra code, que no cambia:

HTTP/1.1 409 Conflict
{
  "error": {
    "code": "CHOQUE",
    "message": "Ese horario se cruza con otra cita (de 09:00 a 09:30). Elija otra hora."
  }
}
HTTPCódigosCuándo
400SOLICITUD_INVALIDA, PARAMETRO_INVALIDO, CAMPO_REQUERIDO, CAMPO_DESCONOCIDO, VALOR_INVALIDO, JSON_INVALIDO, UN_CAMBIO_A_LA_VEZEl cuerpo o un filtro no sirve; «campo» dice cuál.
400FUERA_DE_HORARIO, DESCANSO, BLOQUEO, FESTIVO, NO_ATIENDE, FECHA_PASADA_SIN_MOTIVO, NO_SE_MUEVELa agenda no admite esa hora (las mismas reglas del panel).
401NO_AUTENTICADO, CLAVE_INVALIDA, CLAVE_REVOCADA, CLAVE_SIN_TITULARFalta la clave, no existe, fue revocada o quien la creó ya no es titular.
402CUENTA_VENCIDA_SOLO_LECTURALa suscripción está vencida o cancelada: leer y exportar siguen abiertos.
403PERMISO_INSUFICIENTE, PLAN_SIN_API, CUENTA_ARCHIVADA, FIRMA_SOLO_EN_PANELLa clave no tiene ese permiso, o la operación no está disponible por la API.
404NO_ENCONTRADO, PACIENTE_NO_ENCONTRADO, CITA_NO_ENCONTRADA, PROFESIONAL_NO_ENCONTRADO, RUTA_NO_ENCONTRADANo existe en esta cuenta. Un id de otra cuenta responde igual.
409CHOQUE, PACIENTE_EXISTE, BORRADOR_EXISTENTE, PAGO_EN_CURSO, EXPORTACION_EN_CURSO, EXPORTACION_EN_PROCESOConflicto con el estado actual; en un duplicado, «existente_id» trae el id del que ya existe.
410EXPORTACION_VENCIDAEl archivo de la exportación ya no está (dura 7 días).
413CUERPO_DEMASIADO_GRANDEEl cuerpo pasa de 1 MB.
429DEMASIADAS_SOLICITUDES, DEMASIADOS_INTENTOS, DEMASIADAS_EXPORTACIONESPasó un límite; espere lo que diga Retry-After.
500ERROR_INTERNOError de Doctia. Reintente; si sigue, escríbanos con la hora de la solicitud.

Endpoints

Base: https://doctia.co/api/v1. Todos responden JSON: una lista va en data (arreglo, con paginacion) y un elemento, en data (objeto). En los ejemplos, $DOCTIA_CLAVE es su clave.

Cuenta

GET/cuentasin permiso

La cuenta, la clave y sus permisos. Úselo para probar la clave.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/cuenta"

Pacientes

GET/pacientespacientes:leer

Lista paginada. Filtros: actualizados_desde (instante ISO), documento, tipo_documento.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/pacientes?documento=1053773323"

GET/pacientes/{id}pacientes:leer

Un paciente.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/pacientes/6538"

POST/pacientespacientes:escribir

Crea un paciente. Obligatorios: tipo_documento, numero_documento, nombres y apellidos. Un documento repetido da 409 PACIENTE_EXISTE con existente_id.

curl -X POST -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"tipo_documento":"CC","numero_documento":"1053773323","nombres":"Laura","apellidos":"Gómez","telefono":"3001234567"}' \
  "https://doctia.co/api/v1/pacientes"

PATCH/pacientes/{id}pacientes:escribir

Cambia solo los campos enviados (null borra el valor).

curl -X PATCH -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"correo":"laura@ejemplo.co","eps":"Sura"}' \
  "https://doctia.co/api/v1/pacientes/6538"

Citas

GET/citascitas:leer

Lista paginada. Filtros: desde y hasta (fechas de Colombia), profesional_id, paciente_id, estado (varios separados por coma) y actualizadas_desde.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/citas?desde=2026-10-20&hasta=2026-10-24&estado=pendiente,confirmada"

GET/citas/{id}citas:leer

Una cita.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/citas/9123"

POST/citascitas:escribir

Agenda con las reglas de la agenda del panel: jornada, descanso, bloqueos, festivos y sin cruces (409 CHOQUE). La duración sale del servicio. Por la API no se agenda en sobrecupo.

curl -X POST -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"paciente_id":6538,"profesional_id":12,"servicio_id":40,"fecha":"2026-10-20","hora":"09:00"}' \
  "https://doctia.co/api/v1/citas"

PATCH/citas/{id}citas:escribir

Cambia el estado (pendiente, confirmada, atendida, cancelada, no_asistio) o reprograma (fecha, hora, profesional_id). Uno de los dos por solicitud.

curl -X PATCH -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"fecha":"2026-10-21","hora":"10:30"}' \
  "https://doctia.co/api/v1/citas/9123"

POST/citas/{id}/cancelarcitas:escribir

Cancela. Motivos: paciente_cancelo, reprogramada, no_confirmo, salud_fuerza_mayor, costo, consultorio, error_agenda u otro (con «detalle»). Algunas cuentas exigen el motivo.

curl -X POST -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"motivo":"paciente_cancelo"}' \
  "https://doctia.co/api/v1/citas/9123/cancelar"

Historias clínicas y evoluciones

GET/historiashistorias:leer

Firmadas y borradores, con contenido y diagnósticos CIE-10. Filtros: paciente_id, profesional_id, estado (borrador o firmada), desde, hasta, actualizadas_desde.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/historias?estado=firmada&actualizadas_desde=2026-10-01T00:00:00Z"

GET/historias/{id}historias:leer

Una historia con sus notas aclaratorias y su sello SHA-256.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/historias/2210"

POST/historiashistorias:escribir

Crea el BORRADOR de una evolución a nombre del profesional que la firmará. La firma solo se hace en el panel (403 FIRMA_SOLO_EN_PANEL).

curl -X POST -H "Authorization: Bearer $DOCTIA_CLAVE" \
  -H "Content-Type: application/json" \
  -d '{"paciente_id":6538,"cita_id":9123,"analisis":"Evolución favorable.","diagnosticos":[{"codigo":"F900"}]}' \
  "https://doctia.co/api/v1/historias"

Paquetes y sesiones

GET/paquetespaquetes:leer

Paquetes con sus líneas y cada sesión: estado de la cita y de la firma de asistencia. Filtros: paciente_id, estado.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/paquetes?paciente_id=6538"

GET/paquetes/{id}paquetes:leer

Un paquete.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/paquetes/31"

Profesionales, servicios y sedes

GET/profesionalescatalogo:leer

Activos (activos=todos incluye los inactivos), con los servicios de cada uno.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/profesionales"

GET/profesionales/{id}catalogo:leer

Un profesional.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/profesionales/12"

GET/servicioscatalogo:leer

Servicios con duración, precio y profesionales.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/servicios"

GET/servicios/{id}catalogo:leer

Un servicio.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/servicios/40"

GET/sedescatalogo:leer

Sedes del consultorio.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/sedes"

GET/sedes/{id}catalogo:leer

Una sede.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/sedes/3"

Exportación

POST/exportacionesexportar

Crea un trabajo asíncrono (202). Una en curso por cuenta y hasta 10 cada 24 horas.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" -X POST \
  "https://doctia.co/api/v1/exportaciones"

GET/exportacionesexportar

Las últimas 20 exportaciones.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/exportaciones"

GET/exportaciones/{id}exportar

Estado: pendiente, procesando, lista, fallida o vencida.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/exportaciones/exp_3f9a0c1b2d4e5f60718293a4"

GET/exportaciones/{id}/archivoexportar

El ZIP, cuando está lista.

curl -H "Authorization: Bearer $DOCTIA_CLAVE" \
  "https://doctia.co/api/v1/exportaciones/exp_3f9a0c1b2d4e5f60718293a4/archivo" \
  -o doctia.zip

Webhooks

En RIPS y API › API y webhooks el titular registra una URL https:// y elige los eventos. Doctia envía un POST con JSON en cuanto el evento ocurre, sin demorar ni frenar la acción en el panel. Al crear el webhook se muestra su secreto (whsec_…) una sola vez; con él verifica la firma. El botón «Enviar prueba» manda un evento prueba y muestra la respuesta de su servidor.

EventoCuándodatos
cita.creadaSe agenda una cita (panel, página pública, API o pago en línea confirmado).{ cita, estado_anterior }
cita.actualizadaCambia la fecha, la hora, el profesional, el servicio o el estado (confirmada, no asistió…).{ cita, estado_anterior }
cita.atendidaLa cita se marca como atendida.{ cita, estado_anterior }
cita.canceladaLa cita se cancela.{ cita, estado_anterior }
historia.firmadaEl profesional firma una historia o evolución. Lleva el resumen sin contenido clínico.{ historia }
paciente.creadoSe registra un paciente nuevo.{ paciente }
sesion.firmadaEl paciente firma la asistencia a una sesión.{ sesion }

Encabezados

  • X-Doctia-Firma: sha256= seguido del HMAC-SHA256 en hexadecimal del cuerpo exacto, con el secreto del webhook.
  • X-Doctia-Evento: el nombre del evento.
  • X-Doctia-Evento-Id: id del evento, igual en todos los reintentos (para no procesarlo dos veces).
  • X-Doctia-Entrega: id de la entrega.
POST /su/ruta HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Doctia-Webhooks/1.0 (+https://doctia.co/desarrolladores)
X-Doctia-Evento: cita.creada
X-Doctia-Evento-Id: evt_5b1e0c2a9f3d4e7a8b6c1d2e3f4a5b6c
X-Doctia-Entrega: 4812
X-Doctia-Firma: sha256=3f1c…

{
  "id": "evt_5b1e0c2a9f3d4e7a8b6c1d2e3f4a5b6c",
  "evento": "cita.creada",
  "creado_en": "2026-10-08T19:17:39Z",
  "cuenta_id": 65,
  "prueba": false,
  "datos": {
    "cita": { "id": 9123, "estado": "pendiente", "fecha": "2026-10-20", "hora_inicio": "09:00", … },
    "estado_anterior": null
  }
}

Entrega y reintentos

Una respuesta 2xx en menos de 10 segundos la da por entregada. Si no, Doctia reintenta a los 1 min, 5 min, 30 min, 2 h, 6 h y 12 h (7 intentos en total) con el mismo cuerpo; no sigue redirecciones. En el panel se ven las últimas entregas de cada webhook con su resultado. Los eventos de cita llevan la cita tal como quedó en ese momento; la historia firmada llega sin contenido clínico: consúltelo con GET /historias/{id} y el permiso historias:leer.

Verificar la firma en Node.js

// Node.js con Express: valide la firma sobre el cuerpo EXACTO (sin volver a serializarlo).
const crypto = require('crypto');
const express = require('express');
const app = express();

app.post('/doctia/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const esperada = 'sha256=' + crypto
    .createHmac('sha256', process.env.DOCTIA_WEBHOOK_SECRETO)
    .update(req.body)            // Buffer con los bytes recibidos
    .digest('hex');
  const recibida = req.get('X-Doctia-Firma') || '';
  const valida = recibida.length === esperada.length
    && crypto.timingSafeEqual(Buffer.from(recibida), Buffer.from(esperada));
  if (!valida) return res.status(401).end();

  const evento = JSON.parse(req.body.toString('utf8'));
  // Guarde evento.id: si llega otra vez (reintento), no lo procese dos veces.
  // Responda rápido (menos de 10 s) y haga el trabajo pesado en segundo plano.
  res.status(200).end();
});

Verificar la firma en Python

# Python con Flask: compare con hmac.compare_digest (tiempo constante).
import hashlib, hmac, json, os
from flask import Flask, abort, request

app = Flask(__name__)
SECRETO = os.environ["DOCTIA_WEBHOOK_SECRETO"].encode()

@app.post("/doctia/webhook")
def recibir():
    cuerpo = request.get_data()  # bytes exactos recibidos
    esperada = "sha256=" + hmac.new(SECRETO, cuerpo, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(esperada, request.headers.get("X-Doctia-Firma", "")):
        abort(401)
    evento = json.loads(cuerpo)
    # evento["id"] es el mismo en los reintentos: úselo para no repetir el trabajo.
    return "", 200

Exportación completa

Un ZIP con LEEME.txt y, por cada entidad (pacientes, citas, historias, notas aclaratorias, paquetes, sesiones, profesionales, servicios y sedes), un archivo JSON con la misma forma de la API y un CSV en UTF-8 listo para Excel. Es asíncrona, funciona también con la cuenta en solo lectura y cada archivo dura 7 días. Contiene datos de salud: guárdelo en un lugar seguro.

# 1. Crear el trabajo
curl -X POST -H "Authorization: Bearer $DOCTIA_CLAVE" https://doctia.co/api/v1/exportaciones
# → 202 { "data": { "id": "exp_3f9a…", "estado": "pendiente", … } }

# 2. Consultar hasta que el estado sea "lista"
curl -H "Authorization: Bearer $DOCTIA_CLAVE" https://doctia.co/api/v1/exportaciones/exp_3f9a…

# 3. Descargar el ZIP (dura 7 días)
curl -H "Authorization: Bearer $DOCTIA_CLAVE" -o doctia.zip \
  https://doctia.co/api/v1/exportaciones/exp_3f9a…/archivo

Guías de integración

Contabilidad

  • Suscriba cita.atendida para registrar el ingreso de cada consulta.
  • Use GET /servicios para el precio de cada servicio y GET /citas?estado=atendida&desde=…&hasta=… para cuadrar el mes.
  • Para una conciliación completa, la exportación trae citas, servicios y paquetes con su valor.

Facturación electrónica

  • Doctia no factura: su sistema de facturación emite la factura electrónica.
  • Con cita.atendida sabe qué consulta facturar; con historia.firmada sabe que la atención ya está cerrada.
  • Consulte GET /historias/{id} para los diagnósticos CIE-10 y la clasificación RIPS (CUPS, finalidad, causa externa), y GET /pacientes/{id} para el documento y los datos del paciente.
  • El número de la factura se escribe luego en Doctia (RIPS del mes) para generar el RIPS por factura.

BI y tableros

  • Carga inicial con la exportación completa (CSV o JSON).
  • Luego, sincronización incremental cada cierto tiempo con actualizadas_desde en citas e historias y actualizados_desde en pacientes, recorriendo las páginas con despues_de. Guarde el instante de cada corrida para la siguiente.
  • Respete el límite de 120 solicitudes por minuto: 200 elementos por página alcanzan para miles de registros en pocas solicitudes.

CRM

  • paciente.creado crea el contacto; cita.creada, cita.atendida, cita.cancelada y cita.actualizada (no asistió) alimentan el historial y las campañas de seguimiento.
  • Para agendar desde el CRM: POST /pacientes (o búsquelo con GET /pacientes?documento=…) y luego POST /citas. Si la hora ya no está libre, la respuesta es 409 CHOQUE y no se crea nada.

Logística de atención domiciliaria

  • La ruta del día de cada profesional: GET /citas?profesional_id=…&desde=…&hasta=…&estado=pendiente,confirmada, con la dirección del paciente en GET /pacientes/{id}.
  • cita.creada, cita.actualizada y cita.cancelada mantienen la ruta al día sin consultar a cada rato.
  • Al terminar la visita, PATCH /citas/{id} con {"estado":"atendida"}; si la cuenta usa paquetes, sesion.firmada confirma la firma de asistencia.

Referencia OpenAPI

La especificación OpenAPI 3 completa (esquemas de cada recurso, parámetros y respuestas) está en /api/v1/openapi.json: impórtela en Postman, Insomnia o su generador de clientes. También puede verla en formato navegable.

Doctia

Software de historia clínica para consultorios en Colombia, con su propia página web para recibir citas.

Producto

  • Software de historia clínica
  • Historia clínica electrónica
  • Página web con reservas
  • Precio
  • Especialidades

Para su consultorio

  • Historia clínica según la Res. 1995
  • Software de citas médicas
  • Página web para psicólogos
  • Recordatorios de citas
  • RIPS en JSON

Recursos

  • Normativa en Colombia
  • Guías para el consultorio
  • Calculadoras médicas
  • IA para médicos
  • Desarrolladores (API)

Contacto

  • WhatsApp +57 310 528 5613
  • Ingresar
  • Crear mi cuenta gratis
© 2026 DoctiaTérminos · Privacidad