Guía de integración de OmniMed Consulta
Enviá la grabación de una consulta y recibí la nota, los pedidos con su borrador de orden y las sugerencias para el médico, en un solo resultado.
OpenAPI 3.1: /openapi-consulta-v1.json
OmniMed Consulta procesa la grabación completa de una consulta médica (médico, paciente y acompañantes) y devuelve, en un único JSON:
- la nota de la consulta en el formato elegido (SOAP por defecto), donde cada oración cita el momento de la conversación del que sale;
- los pedidos que hizo el médico (laboratorio, imágenes, otros estudios, derivaciones, terapias, procedimientos), cada uno con un borrador de orden listo para completar en tu plataforma;
- las sugerencias para el médico (diagnósticos para considerar con razones a favor y en contra, controles de seguridad de los pedidos, chequeos de guías clínicas), que nunca cambian la nota por sí solas;
- los códigos CIE-10 propuestos para la evaluación;
- la transcripción con el rol de cada hablante y sus tiempos.
Si enviás la historia clínica del paciente, el resultado la usa como contexto: trae los antecedentes relevantes citados y marca las discrepancias entre lo dicho hoy y lo registrado (por ejemplo, "no toma medicación" frente a un tratamiento activo). La nota sale solo de lo dicho en la consulta de hoy: la historia nunca entra en sus secciones como si se hubiera dicho.
Las decisiones del médico (aceptar una sugerencia, editar la nota, firmar las órdenes) ocurren en tu sistema, no en el nuestro.
El contrato completo está en OpenAPI 3.1.
Inicio rápido
URL base: https://api.omniai.com.ar
-
Pedí tu Partner Credential (credencial de partner) a santiago@omniai.com.ar, habilitada para Consulta en tus centros. Es un secreto
omp_...: guardalo como variable de entorno de tu servidor. Si ya integrás el dictado de radiología, la misma credencial sirve cuando el centro tiene Consulta habilitada. -
Enviá la consulta. Desde tu servidor, con la grabación y el consentimiento del paciente:
curl -X POST https://api.omniai.com.ar/api/consulta/v1/consultations \ -H "Authorization: Bearer $OMNI_PARTNER_CREDENTIAL" \ -H "Idempotency-Key: 6f1c2a4e-0b9d-4d61-a3f2-7c5e8b1d9a20" \ -F "audio=@consulta.m4a" \ -F "medical_center_id=YOUR_CENTER_ID" \ -F "doctor_id=med-1042" \ -F "consent=true"Responde
202enseguida, con elidde la consulta ystatus: "processing". -
Consultá el resultado hasta que
statusseadone(ofailed):curl https://api.omniai.com.ar/api/consulta/v1/consultations/3f6c1d2e-8a4b-4c7e-9f10-2b5d6e7a8c90 \ -H "Authorization: Bearer $OMNI_PARTNER_CREDENTIAL" -
Mostrá el resultado en tu sistema: la nota en
note, los pedidos enorders, las sugerencias ensuggestions(ver El resultado, campo por campo).
El procesamiento es asíncrono porque una consulta larga tarda más de lo que una conexión HTTP espera.
Autenticación
Partner Credential (servidor a servidor)
Cada llamada lleva la Partner Credential como Bearer:
Authorization: Bearer omp_...- Es un secreto de servidor a servidor: nunca debe llegar al navegador ni a una app instalada. Guardala en una variable de entorno o en un gestor de secretos.
- Está habilitada para una lista de centros y, en cada centro, para los productos contratados. Una credencial de Dictado no sirve para Consulta salvo que el centro tenga Consulta habilitada para esa credencial.
- Cada llamada que crea una consulta nombra el centro con
medical_center_id(su id o su slug en Omnimed AI). - Esta API no tiene un token para el navegador: todas las llamadas salen de tu servidor.
Rotación
Si la credencial se filtra, pedinos rotarla a santiago@omniai.com.ar: la credencial vieja deja de funcionar en menos de 60 segundos. Una credencial rotada o revocada responde 401 con partner_credential_revoked.
Médico declarado
Tu sistema ya autenticó al médico; en cada consulta nos decís quién es. No creamos usuarios ni le pedimos al médico que inicie sesión con nosotros: confiamos en tu palabra dentro de los centros para los que tu credencial está habilitada.
| Campo | Requerido | Máximo | Descripción |
|---|---|---|---|
doctor_id | Sí | 255 caracteres | Tu identificador del médico que grabó la consulta |
doctor_name | No | 255 caracteres | Nombre del médico, como debe figurar en el borrador de orden |
doctor_license | No | 100 caracteres | Matrícula del médico. Sin ella, el borrador de orden la marca como faltante (missing incluye doctor_license) |
doctor_specialty | No | 255 caracteres | Especialidad del médico |
Los cuatro vuelven en la consulta como doctor (id, name, license, specialty). Un campo que supera su largo máximo responde 422 con invalid_doctor.
Decidí el médico en tu backend a partir de tu propia sesión, nunca a partir de algo que mande el navegador: así un médico no puede actuar a nombre de otro.
Datos del paciente
No hace falta identificar al paciente. Podés enviar en el campo patient (JSON) datos opcionales que mejoran el resultado y una referencia opaca tuya para correlacionar:
| Campo | Tipo | Descripción |
|---|---|---|
ref | String (hasta 255) | Referencia opaca de tu sistema (por ejemplo, el número de historia clínica). Vuelve como patient_ref; no la usamos para unir consultas |
birth_date | Fecha (AAAA-MM-DD) | Fecha de nacimiento. Si la enviás, se usa en lugar de age |
age | Entero, 0 a 130 | Edad en años |
sex | female, male, other, unknown | Sexo |
coverage.name | String | Obra social o prepaga |
coverage.plan | String | Plan |
coverage.affiliate_number | String | Número de afiliado |
{"ref": "hc-998877", "birth_date": "1988-04-12", "sex": "female", "coverage": {"name": "Obra social ejemplo", "plan": "210"}}- Sin identidad. No mandes nombre ni documento: los datos de identidad del paciente los completa tu sistema sobre el borrador de orden.
- Tolerante. Los campos que no conocemos se ignoran, y también un campo conocido cuyo valor no respeta el formato (por ejemplo
sex: "F",age: 200o una fecha mal escrita): se descarta ese campo y se usa el resto. Guardamos solo los campos de esta lista. - Hasta 1 MB. Un
patientque no es JSON, no es un objeto o supera 1 MB responde422coninvalid_patient.
Historia clínica
Opcional, en el campo history (JSON), organizada por secciones. Todas las secciones son opcionales. Cada registro lleva tu propio id: las citas del resultado (source_ids) usan esos mismos ids, así podés enlazar cada antecedente o discrepancia al registro de tu sistema.
| Sección | Campos requeridos | Campos opcionales |
|---|---|---|
problems | id, text (el problema, como lo lista tu sistema, ej. "Hipertensión arterial") | status (active o resolved; por defecto active), code (código con su sistema, ej. "CIE-10 I10"), onset (fecha) |
allergies | id, substance | reaction |
medications | id, name | dose (dosis y frecuencia en texto, ej. "10 mg cada 12 h"), status (active o suspended; por defecto active), since (fecha) |
labs | id, name, value, date | unit, ref_range (rango de referencia en texto, ej. "70-110") |
imaging_reports | id, study, date, text (texto del informe) | conclusion |
visits (consultas previas) | id, date | reason, summary (resumen o evolución de esa consulta) |
Las fechas van como AAAA-MM-DD.
{
"problems": [{"id": "pb-3", "text": "Hipertensión arterial", "code": "CIE-10 I10", "onset": "2019-05-01"}],
"allergies": [{"id": "alg-17", "substance": "Penicilina", "reaction": "Urticaria"}],
"medications": [{"id": "med-8", "name": "Enalapril", "dose": "10 mg cada 12 h", "since": "2019-05-01"}],
"labs": [{"id": "lab-51", "name": "Glucemia", "value": "98", "unit": "mg/dL", "ref_range": "70-110", "date": "2026-08-20"}],
"imaging_reports": [
{"id": "img-4", "study": "Ecografía abdominal", "date": "2025-03-10", "text": "Hígado de forma y tamaño conservados...", "conclusion": "Sin hallazgos patológicos."}
],
"visits": [{"id": "vis-22", "date": "2026-08-20", "reason": "Control de presión", "summary": "Buen control con enalapril."}]
}- La historia que enviás se usa solo para esa consulta: nunca se mezcla con otra consulta ni con otro paciente.
- Hasta 1 MB de JSON. Los textos de más de 8.000 caracteres se recortan.
- Responde
422coninvalid_historysi no es JSON, supera 1 MB o un registro no respeta su formato: campos requeridos, fechas, valores permitidos o ids repetidos dentro de una sección.
Consentimiento
Grabar una consulta requiere el consentimiento del paciente. Al enviar consent=true tu sistema declara que el paciente aceptó ser grabado. Sin eso la consulta se rechaza con 422 y consent_required.
Endpoints
Todas las llamadas llevan Authorization: Bearer omp_... y salen de tu servidor.
| Método | Ruta | Para qué |
|---|---|---|
POST | /api/consulta/v1/consultations | Enviar una consulta para procesar |
GET | /api/consulta/v1/consultations/{consultation_id} | Obtener el estado y el resultado |
POST | /api/consulta/v1/consultations/{consultation_id}/instructions | Generar indicaciones para el paciente |
GET | /api/consulta/v1/note-formats | Listar los formatos de nota |
Enviar una consulta
POST https://api.omniai.com.ar/api/consulta/v1/consultations
Authorization: Bearer omp_...
Idempotency-Key: <UUID de este envío>
Content-Type: multipart/form-dataRecibe la grabación completa y responde de inmediato con el id; el procesamiento sigue en segundo plano.
Parámetros
| Dónde | Campo | Tipo | Requerido | Descripción |
|---|---|---|---|---|
| Header | Idempotency-Key | String (hasta 255) | No, recomendado | Identificador único de este envío, generado por tu sistema (recomendado un UUID). Ver Idempotencia |
| Form | audio | Archivo | Sí | La grabación completa de la consulta (médico, paciente y acompañantes) |
| Form | dictation | Archivo | No | Lo que el médico dicta solo, después de que el paciente se fue. Se suma a la misma consulta; si contradice a la conversación, prevalece el dictado |
| Form | medical_center_id | String | Sí | El centro por el que actuás (su id o su slug en Omnimed AI). Tiene que estar habilitado para Consulta en tu credencial |
| Form | doctor_id | String (hasta 255) | Sí | Tu identificador del médico. Ver Médico declarado |
| Form | doctor_name | String (hasta 255) | No | Nombre del médico, como debe figurar en el borrador de orden |
| Form | doctor_license | String (hasta 100) | No | Matrícula del médico |
| Form | doctor_specialty | String (hasta 255) | No | Especialidad del médico |
| Form | consent | Boolean | Sí | Tiene que ser true. Declara que el paciente aceptó ser grabado |
| Form | note_format | String | No | Uno de los id de GET /api/consulta/v1/note-formats. Por defecto soap |
| Form | num_speakers | Entero, 1 a 10 | No | Cantidad esperada de personas que hablan. Si no se envía, se detecta sola |
| Form | patient | JSON | No | Ver Datos del paciente |
| Form | history | JSON | No | Ver Historia clínica |
audio y dictation aceptan m4a, mp3, wav, webm y ogg, hasta 100 MB cada uno. Mandá patient e history como partes JSON del formulario (type=application/json en curl).
Respuesta 202: consulta aceptada
{
"id": "3f6c1d2e-8a4b-4c7e-9f10-2b5d6e7a8c90",
"status": "processing",
"stage": "transcribing",
"medical_center_id": "9b2f4c1a-6d3e-4f5a-8b7c-1d2e3f4a5b6c",
"doctor": {"id": "med-1042", "name": "Dra. Ana Pérez", "license": "MP 12345", "specialty": "Clínica médica"},
"patient_ref": "hc-998877",
"note_format": "soap",
"created_at": "2026-11-03T13:05:12Z",
"completed_at": null
}- Guardá el
id: es lo que vas a consultar. medical_center_idviene siempre como id del centro, aunque lo hayas enviado por su slug.- Un reenvío con una
Idempotency-Keyya usada responde200con la consulta existente, con la misma forma; si esa consulta había fallado o estaba trabada, se reprocesa y responde202con el mismoid(ver Idempotencia).
Ejemplo completo
Con dictado posterior, datos del médico, datos del paciente e historia clínica:
curl -X POST https://api.omniai.com.ar/api/consulta/v1/consultations \
-H "Authorization: Bearer $OMNI_PARTNER_CREDENTIAL" \
-H "Idempotency-Key: 6f1c2a4e-0b9d-4d61-a3f2-7c5e8b1d9a20" \
-F "audio=@consulta.m4a" \
-F "dictation=@dictado-posterior.m4a" \
-F "medical_center_id=YOUR_CENTER_ID" \
-F "doctor_id=med-1042" \
-F "doctor_name=Dra. Ana Pérez" \
-F "doctor_license=MP 12345" \
-F "doctor_specialty=Clínica médica" \
-F "consent=true" \
-F "note_format=soap" \
-F 'patient={"ref": "hc-998877", "age": 38, "sex": "female"};type=application/json' \
-F "history=@historia.json;type=application/json"En Python:
import json
import os
import uuid
import requests
API = "https://api.omniai.com.ar"
headers = {
"Authorization": f"Bearer {os.environ['OMNI_PARTNER_CREDENTIAL']}",
"Idempotency-Key": str(uuid.uuid4()), # guardala para reintentar con la misma
}
with open("consulta.m4a", "rb") as audio:
response = requests.post(
f"{API}/api/consulta/v1/consultations",
headers=headers,
files={
"audio": ("consulta.m4a", audio, "audio/mp4"),
"patient": (None, json.dumps({"ref": "hc-998877", "age": 38}), "application/json"),
},
data={
"medical_center_id": "YOUR_CENTER_ID",
"doctor_id": "med-1042",
"consent": "true",
},
timeout=120,
)
response.raise_for_status()
consultation_id = response.json()["id"]Obtener el estado y el resultado
GET https://api.omniai.com.ar/api/consulta/v1/consultations/{consultation_id}
Authorization: Bearer omp_...consultation_id es el id que devolvió el envío.
Estados
status | Qué significa | Qué hacer |
|---|---|---|
processing | Se está procesando. stage dice en qué paso va y los campos del resultado vienen vacíos | Seguí consultando |
done | Terminó. Trae todo el resultado | Mostralo en tu sistema |
failed | Falló. error dice por qué | Reenviá con la misma Idempotency-Key |
Mientras status es processing, stage va pasando por transcribing, identifying_speakers y writing_note; al terminar vale done o failed. La lista de pasos puede crecer: tratá un valor que no conozcas como "procesando".
Con failed, error trae uno de estos códigos:
error.code | Qué significa | error.message |
|---|---|---|
audio_empty | La grabación no tiene voz | "La grabación no tiene voz. Revisá que el micrófono no haya estado silenciado." |
processing_failed | El procesamiento falló | "No se pudo procesar la consulta; reenviala con la misma Idempotency-Key." |
processing_timeout | El procesamiento llevaba más de 10 minutos sin avanzar | "El procesamiento se interrumpió; reenviala con la misma Idempotency-Key." |
Un código que no conozcas trae el mensaje "No se pudo procesar la consulta.". Compará por error.code; error.message es para mostrarle al médico y puede cambiar.
{
"id": "3f6c1d2e-8a4b-4c7e-9f10-2b5d6e7a8c90",
"status": "failed",
"stage": "failed",
"error": {"code": "audio_empty", "message": "La grabación no tiene voz. Revisá que el micrófono no haya estado silenciado."},
"...": "..."
}En los tres casos podés reenviar con la misma Idempotency-Key: la consulta se procesa de nuevo con el audio de ese envío y responde 202 con el mismo id.
Cada cuánto consultar
Recomendamos consultar cada 5 segundos durante el primer minuto y después cada 15 segundos. El tiempo de procesamiento depende de la duración del audio; no hay un tiempo garantizado.
import time
def wait_for(consultation_id: str) -> dict:
started = time.monotonic()
while True:
response = requests.get(
f"{API}/api/consulta/v1/consultations/{consultation_id}",
headers={"Authorization": f"Bearer {os.environ['OMNI_PARTNER_CREDENTIAL']}"},
timeout=30,
)
response.raise_for_status()
consultation = response.json()
if consultation["status"] != "processing":
return consultation # "done" o "failed"
time.sleep(5 if time.monotonic() - started < 60 else 15)Respuesta 200 con status: "done"
{
"id": "3f6c1d2e-8a4b-4c7e-9f10-2b5d6e7a8c90",
"status": "done",
"stage": "done",
"error": null,
"medical_center_id": "9b2f4c1a-6d3e-4f5a-8b7c-1d2e3f4a5b6c",
"doctor": {"id": "med-1042", "name": "Dra. Ana Pérez", "license": "MP 12345", "specialty": "Clínica médica"},
"patient_ref": "hc-998877",
"note_format": "soap",
"created_at": "2026-11-03T13:05:12Z",
"completed_at": "2026-11-03T13:06:40Z",
"audio_seconds": 312.4,
"note": {
"format": "soap",
"sections": [
{
"key": "subjective",
"title": "Subjetivo",
"sentences": [
{"text": "Dolor abdominal de 2 días de evolución, con diarrea, sin vómitos.", "turn_ids": ["t2", "t4"]},
{"text": "Refiere cefalea leve.", "turn_ids": ["t4"]}
]
},
{
"key": "objective",
"title": "Objetivo",
"sentences": [{"text": "Dolor que se irradia a fosa ilíaca derecha.", "turn_ids": ["t8"]}]
},
{
"key": "assessment",
"title": "Evaluación",
"sentences": [{"text": "Se sospecha cuadro quirúrgico a descartar.", "turn_ids": ["t9"]}]
},
{
"key": "plan",
"title": "Plan",
"sentences": [
{"text": "Laboratorio con enzimas y ecografía abdominal; control con resultados.", "turn_ids": ["t9"]},
{"text": "Pautas de alarma: fiebre alta, dolor abdominal intenso o abdomen duro.", "turn_ids": ["t11"]}
]
}
],
"history_context": [{"text": "Alergia a penicilina registrada.", "source_ids": ["alg-17"]}],
"discrepancies": []
},
"orders": [
{
"id": "o1",
"kind": "lab",
"name": "Laboratorio",
"said_as": "te vamos a sacar sangre, quiero ver si tenés elevadas algunas enzimas",
"details": "Enzimas",
"indication": "Descartar cuadro quirúrgico",
"status": "firm",
"needs_detail": true,
"detail_question": "¿Qué determinaciones?",
"detail_options": ["Hemograma", "Hepatograma", "Amilasa", "Lipasa"],
"turn_ids": ["t9"],
"missing": ["detail"],
"draft": {
"study": "Laboratorio, Enzimas",
"presumptive_diagnosis": "Dolor abdominal, se sospecha cuadro quirúrgico",
"clinical_data": "Dolor abdominal de 2 días con irradiación a fosa ilíaca derecha.",
"cie10_codes": ["R10.3"]
}
},
{
"id": "o2",
"kind": "imaging",
"name": "Ecografía abdominal",
"said_as": "vamos a hacer un eco rápido como para descartar que no sea el apéndice",
"details": "",
"indication": "Descartar apendicitis",
"status": "firm",
"needs_detail": false,
"detail_question": "",
"detail_options": [],
"turn_ids": ["t9"],
"missing": [],
"draft": {
"study": "Ecografía abdominal",
"presumptive_diagnosis": "Dolor abdominal, se sospecha cuadro quirúrgico",
"clinical_data": "Dolor abdominal de 2 días con irradiación a fosa ilíaca derecha.",
"cie10_codes": ["R10.3"]
}
}
],
"suggestions": [
{
"id": "s1",
"kind": "diagnosis_to_consider",
"severity": "info",
"title": "Gastroenteritis aguda",
"body": "",
"question": "",
"order_id": null,
"proposed_actions": [
{"key": "add_to_assessment", "label": "Agregar a la evaluación"},
{"key": "dismiss", "label": "Descartar"}
],
"reasons_for": [{"text": "Diarrea de 2 días", "turn_ids": ["t4"]}],
"reasons_against": [{"text": "Dolor que se irradia a fosa ilíaca derecha", "turn_ids": ["t8"]}],
"sources": {"turn_ids": ["t4", "t8"], "source_ids": [], "guideline": null},
"ai_generated": true
}
],
"diagnosis_codes": [{"code": "R10.3", "description": "Dolor localizado en otras partes inferiores del abdomen"}],
"transcript": {
"turns": [
{"id": "t1", "speaker_id": "speaker_0", "role": "doctor", "segment": "conversation", "text": "Hola, ¿cómo andás?", "start": 0.4, "end": 1.6},
{"id": "t2", "speaker_id": "speaker_1", "role": "patient", "segment": "conversation", "text": "Vengo porque tengo dolor de panza hace dos días.", "start": 1.8, "end": 5.2}
]
},
"patient_instructions": null
}La transcripción del ejemplo está recortada a sus dos primeros turnos. Cada campo se explica en El resultado, campo por campo.
Generar indicaciones para el paciente
POST https://api.omniai.com.ar/api/consulta/v1/consultations/{consultation_id}/instructions
Authorization: Bearer omp_...Sin cuerpo. Reescribe el Plan de la nota en lenguaje simple para que el paciente se lo lleve (por ejemplo, por WhatsApp o impreso). No agrega nada que no esté en la nota.
- Solo con una consulta en
done; antes responde409conconsultation_not_done. - Si la nota no tiene Plan del que partir, responde
422conplan_empty. - Llamarlo de nuevo genera una versión nueva, que reemplaza a la anterior en
patient_instructionsde la consulta. - No cuenta como consulta.
Respuesta 200
{
"patient_instructions": "Vas a hacerte un análisis de sangre y una ecografía de abdomen. Cuando tengas los resultados, volvé a la consulta.\nConsultá a la guardia si tenés fiebre alta, dolor fuerte en la panza o si la panza se pone dura de golpe.\n"
}patient_instructions es texto plano, en segunda persona, para el paciente.
Ejemplo
curl -X POST https://api.omniai.com.ar/api/consulta/v1/consultations/3f6c1d2e-8a4b-4c7e-9f10-2b5d6e7a8c90/instructions \
-H "Authorization: Bearer $OMNI_PARTNER_CREDENTIAL"Listar los formatos de nota
GET https://api.omniai.com.ar/api/consulta/v1/note-formats
Authorization: Bearer omp_...Los formatos en los que se puede escribir la nota, con sus secciones en orden. El id de cada formato es el valor de note_format al enviar la consulta, y las key de sus secciones son las de note.sections en el resultado.
Respuesta 200
{
"formats": [
{
"id": "soap",
"name": "SOAP",
"sections": [
{"key": "subjective", "title": "Subjetivo"},
{"key": "objective", "title": "Objetivo"},
{"key": "assessment", "title": "Evaluación"},
{"key": "plan", "title": "Plan"}
]
},
{
"id": "pediatric_checkup",
"name": "Control pediátrico",
"sections": [
{"key": "reason", "title": "Motivo de consulta"},
{"key": "growth", "title": "Crecimiento y desarrollo"},
{"key": "feeding", "title": "Alimentación y hábitos"},
{"key": "vaccines", "title": "Vacunas"},
{"key": "exam", "title": "Examen físico"},
{"key": "plan", "title": "Indicaciones"}
]
}
]
}Idempotencia
Una red que se corta a mitad de un envío de 15 MB deja la duda de si la consulta llegó. Con Idempotency-Key podés reenviar sin crear otra consulta ni pagarla dos veces.
- Generá un identificador único por consulta (recomendado un UUID, hasta 255 caracteres) y guardalo junto a la consulta en tu sistema antes de enviar.
- Reenvío con la misma key:
- si la primera sigue en proceso o terminó, responde
200con esa misma consulta; - si falló, o lleva más de 10 minutos sin avanzar, se procesa de nuevo con el audio de este envío y responde
202con el mismoid: seguí consultando eseid.
- si la primera sigue en proceso o terminó, responde
- Fallida o trabada: para reintentar una consulta en
failed(o una que no avanza), reenviala con la misma key. - Keys por credencial: cada credencial tiene sus propias keys. Una key que ya se usó para otro centro responde
409conidempotency_key_conflict. - Sin key, cada envío crea una consulta nueva.
- Una key de más de 255 caracteres responde
422coninvalid_idempotency_key.
El resultado, campo por campo
Consulta
| Campo | Tipo | Descripción |
|---|---|---|
id | UUID | Identificador de la consulta |
status | processing, done, failed | Estado. Ver Estados |
stage | String | Paso actual: transcribing, identifying_speakers, writing_note, done, failed. Puede crecer; tratá un valor desconocido como "procesando" |
error | Objeto o null | Solo con failed: {"code", "message"} |
medical_center_id | String | El id del centro, aunque lo hayas enviado por su slug |
doctor | Objeto | El médico declarado: id, name, license, specialty (estos tres pueden ser null) |
patient_ref | String o null | El ref que mandaste en patient |
note_format | String | El formato de la nota |
created_at | Fecha y hora | Cuándo se recibió |
completed_at | Fecha y hora o null | Cuándo terminó |
audio_seconds | Número o null | Duración del audio procesado |
note | Objeto o null | La nota |
orders | Lista | Los pedidos |
suggestions | Lista | Las sugerencias |
diagnosis_codes | Lista | Los códigos CIE-10 |
transcript | Objeto o null | La transcripción |
patient_instructions | String o null | Las últimas indicaciones generadas con POST .../instructions, si las hay |
Parseá la respuesta de forma tolerante: pueden sumarse campos nuevos.
Nota (note)
| Campo | Descripción |
|---|---|
format | El formato de la nota (soap, pediatric_checkup, ...) |
sections | Las secciones del formato, en orden. Cada una tiene key, title y sentences |
sections[].sentences[] | Cada oración: text y turn_ids, los turnos de transcript.turns de los que sale. Cada oración sale de lo dicho hoy |
history_context | Antecedentes de la historia enviada que son relevantes hoy: text y source_ids, los id de tus registros en history. Vacío sin historia |
discrepancies | Lo dicho hoy que contradice la historia enviada: text, turn_ids (el turno) y source_ids (el registro). Nunca se resuelve sola: la resuelve el médico |
history_context y discrepancies van aparte de sections: la historia nunca entra en la nota como si se hubiera dicho hoy.
Pedidos (orders)
Un pedido es algo que indicó el médico, citado a sus propias palabras. Lo que pide el paciente sin que el médico lo indique no es un pedido.
| Campo | Descripción |
|---|---|
id | Identificador del pedido dentro de la consulta (lo cita suggestions[].order_id) |
kind | lab (laboratorio), imaging (imágenes), other_study (otro estudio), referral (derivación), therapy (terapia), procedure (procedimiento) |
name | Nombre clínico, como en una orden (ej. "Ecografía abdominal") |
said_as | Las palabras textuales del médico |
details | Lateralidad, región, contraste, determinaciones, sesiones; solo si se dijeron |
indication | El motivo del pedido, solo si el médico lo dijo |
status | firm: lo pide ahora. conditional: depende de algo futuro ("si sale positivo..."). in_visit: se hace durante la consulta |
needs_detail | true si falta algo imprescindible para emitir la orden |
detail_question | Qué falta, como pregunta corta para el médico (ej. "¿Qué determinaciones?") |
detail_options | Entre 2 y 4 respuestas posibles a detail_question, para mostrar como opciones |
turn_ids | Los turnos donde el médico lo pidió |
missing | Lo que falta para que el borrador de orden esté completo: detail, diagnosis, clinical_data, doctor_license |
draft | El borrador de orden. Solo para pedidos firm; si no, null |
Borrador de orden (draft)
Lo que una plataforma de órdenes necesita, armado a partir del pedido y la nota. Nunca es una orden válida por sí misma: el médico la completa y la firma en tu sistema, y los datos del paciente (nombre, documento, cobertura) los agrega tu sistema.
| Campo | Descripción |
|---|---|
study | Estudio con sus detalles |
presumptive_diagnosis | Diagnóstico presuntivo, de la evaluación de la nota; null si falta |
clinical_data | Datos clínicos que justifican el pedido; null si faltan |
cie10_codes | Hasta 2 códigos CIE-10 |
Sugerencias (suggestions)
Una sugerencia es una propuesta para el médico sobre la consulta de hoy. No cambia nada por sí sola: el médico decide en tu sistema. Cada una explica por qué y cita sus fuentes.
kind | Qué propone |
|---|---|
diagnosis_to_consider | Un diagnóstico para considerar, con razones a favor y en contra |
safety | Un pedido contraindicado para este paciente (ej. contraste tras una reacción previa) |
duplicate | El mismo estudio ya figura en la historia enviada |
low_value | Una guía desaconseja ese pedido en esta situación |
missing_check | Un chequeo de una guía clínica que no aparece en la consulta |
unrecorded_problem | Algo dicho hoy que no figura como problema en la historia |
pending_follow_up | Un control que la historia deja pendiente |
La lista de tipos puede crecer: ignorá los que no conozcas.
| Campo | Descripción |
|---|---|
id | Identificador de la sugerencia |
kind | El tipo, de la lista de arriba |
severity | red: revisar antes de cerrar la consulta. amber: conviene revisar. info: informativa |
title | Título corto |
body | Explicación |
question | Pregunta para el médico, si la sugerencia la tiene |
order_id | El pedido al que se refiere, si se refiere a uno; si no, null |
proposed_actions | Acciones que la sugerencia propone (key, label), para que las muestres. Aplicarlas es decisión del médico en tu sistema |
reasons_for | Solo en diagnosis_to_consider: razones a favor, cada una con text y turn_ids |
reasons_against | Solo en diagnosis_to_consider: razones en contra, con la misma forma |
sources | Sus fuentes: turn_ids (turnos de la transcripción), source_ids (tus registros en history) y guideline (title, url, version de la guía clínica, o null) |
ai_generated | true cuando la sugerencia la redacta un modelo de IA (hoy, los diagnósticos para considerar); false cuando sale de una regla clínica explícita |
Códigos CIE-10 (diagnosis_codes)
Hasta 4 códigos CIE-10 propuestos para la evaluación, cada uno con code (normalizado, ej. "K52.9") y description.
Transcripción (transcript)
transcript.turns es la conversación, turno por turno:
| Campo | Descripción |
|---|---|
id | Lo que citan los turn_ids de la nota, los pedidos y las sugerencias |
speaker_id | Identificador del hablante dentro de la consulta (ej. speaker_0) |
role | doctor, patient, companion (quien acompaña al paciente; lo que dice entra en la nota como referido por un familiar) o unknown (no se pudo asignar) |
segment | conversation, o dictation para el dictado posterior del médico |
text | Lo dicho |
start, end | Segundos desde el inicio de su audio, o null |
Cómo seguir una cita
turn_idsapunta atranscript.turns[].id: con surolesabés quién lo dijo y constartyend, en qué momento.source_idsapunta a losidque vos pusiste enhistory: enlazá el antecedente o la discrepancia a tu propio registro.order_idapunta aorders[].id.
Errores
Todo error responde con un status no-2xx y este cuerpo:
{
"detail": {
"code": "consent_required",
"message": "Falta el consentimiento del paciente (consent=true)."
}
}detail.code es estable y es lo que tu código debe comparar; detail.message es para personas y puede cambiar.
Credencial y disponibilidad (todos los endpoints)
| Status | detail.code | Qué significa | Qué hacer |
|---|---|---|---|
| 401 | unauthenticated | Falta Authorization: Bearer omp_... | Mandá la credencial desde tu servidor |
| 401 | invalid_partner_credential | Credencial desconocida (mal copiada o rotada) | Revisá el valor; si se rotó, usá la nueva |
| 401 | partner_credential_revoked | Credencial revocada | Pedinos una credencial nueva |
| 503 | partner_lookup_unavailable | No pudimos verificar la credencial en este momento. No es un rechazo | Reintentá con espera creciente |
| 503 | llm_unavailable | El modelo de IA no está disponible por un momento | Reintentá con espera creciente |
Los 401 traen además el header WWW-Authenticate.
POST /api/consulta/v1/consultations
| Status | detail.code | Qué significa |
|---|---|---|
| 400 | invalid_audio | El audio falta, pesa menos de 1 KB, su formato no está soportado o su extensión no coincide con el contenido |
| 403 | center_not_enabled | Tu credencial no está habilitada para ese centro |
| 403 | product_not_enabled | El centro no tiene Consulta habilitada para tu credencial |
| 409 | idempotency_key_conflict | La Idempotency-Key ya se usó para otro centro |
| 413 | — | El archivo supera los 100 MB. Esta respuesta puede llegar sin cuerpo JSON: ramificá por el status |
| 422 | medical_center_id_required | Falta medical_center_id |
| 422 | consent_required | Falta consent=true |
| 422 | doctor_id_required | Falta doctor_id |
| 422 | invalid_doctor | Un campo del médico supera su largo máximo |
| 422 | unknown_note_format | note_format no es uno de los de GET /api/consulta/v1/note-formats |
| 422 | invalid_patient | patient no es JSON, no es un objeto o supera 1 MB (un campo con un valor inválido no da error: se descarta) |
| 422 | invalid_history | history no es JSON, supera 1 MB o un registro no respeta su formato (campos requeridos, fechas, valores permitidos o ids repetidos en una sección) |
| 422 | invalid_num_speakers | num_speakers no es un entero entre 1 y 10 |
| 422 | invalid_idempotency_key | La Idempotency-Key supera 255 caracteres |
Los 403 son configuración de nuestro lado: escribinos. Los 400, 409, 413 y 422 no se reintentan sin corregir la llamada.
GET /api/consulta/v1/consultations/{consultation_id}
| Status | detail.code | Qué significa |
|---|---|---|
| 404 | consultation_not_found | La consulta no existe, no es de tu credencial o su centro ya no tiene Consulta habilitada para tu credencial |
Una consulta que falló no es un error HTTP: responde 200 con status: "failed" y el motivo en error (ver Estados).
POST /api/consulta/v1/consultations/{consultation_id}/instructions
| Status | detail.code | Qué significa | Qué hacer |
|---|---|---|---|
| 404 | consultation_not_found | Igual que en el GET | |
| 409 | consultation_not_done | La consulta todavía no terminó de procesarse o falló | Esperá a done |
| 422 | plan_empty | La nota no tiene Plan del que partir | No hay indicaciones que generar |
| 502 | generation_failed | No se pudieron generar | Reintentá |
GET /api/consulta/v1/note-formats
Solo los errores de credencial y disponibilidad.
Límites
| Límite | Valor |
|---|---|
Audio (audio, dictation) | Hasta 100 MB por archivo; mínimo 1 KB. Formatos m4a, mp3, wav, webm y ogg |
Historia clínica (history) | Hasta 1 MB de JSON; los textos de más de 8.000 caracteres se recortan |
Datos del paciente (patient) | Hasta 1 MB de JSON |
Idempotency-Key | 255 caracteres |
doctor_id, doctor_name, doctor_specialty | 255 caracteres |
doctor_license | 100 caracteres |
num_speakers | 1 a 10 |
- Una consulta de 15 minutos en m4a pesa alrededor de 15 MB.
- El tiempo de procesamiento depende de la duración del audio; no hay un tiempo garantizado. Consultá cada 5 segundos durante el primer minuto y después cada 15.
- Reintentá los
5xxcon espera creciente; los4xxno se reintentan sin corregir la llamada.
Datos y seguridad
- Todo el tráfico va cifrado (TLS).
- Nuestros servidores y bases de datos están alojados en la Unión Europea.
- El audio se procesa y no se conserva salvo que el contrato del centro lo pida.
- Qué más se conserva (transcripción, nota, resultado) y por cuánto tiempo se define en el contrato con cada centro.
- La lista de subprocesadores y sus condiciones se entrega bajo acuerdo de confidencialidad.
Facturación
Cada consulta procesada con éxito cuenta como una consulta. No cuentan:
- reenviar con la misma
Idempotency-Key; - una consulta que falla por un problema de nuestro lado;
- generar indicaciones para el paciente.
Changelog
v1 (2026-10-30)
- Primera versión:
POST /api/consulta/v1/consultations,GET /api/consulta/v1/consultations/{consultation_id},POST .../instructionsyGET /api/consulta/v1/note-formats. - Partner Credential (
omp_...) habilitada por centro y producto, con el médico declarado en cada consulta. - Historia clínica opcional con citas a tus propios ids (
source_ids) y discrepancias. - Idempotencia con
Idempotency-Key.
Soporte
Consultas, Partner Credentials y alta de centros: santiago@omniai.com.ar
- Contrato: OpenAPI 3.1 de Consulta
- Documentación de la API de radiología: /api-documentation/radiologia
