Guía de integración de Omni-AI
Integrá la transcripción médica de Omni-AI en tu producto en minutos.
1. Widget
Agregá un tag <script> a tu página. Se inyecta un botón flotante de grabación; el informe generado puede autoinsertarse en el editor activo.
Usá esta opción si: querés la integración más rápida y te sirve la interfaz de grabación por defecto.
2. API directa
Enviá un archivo de audio por POST a /api/omni-ai y manejá la respuesta vos mismo.
Usá esta opción si: necesitás control total de la interfaz de grabación o integrás desde un cliente nativo o de escritorio.
URL base: https://api.omniai.com.ar
Descripción general
Omni-AI convierte un dictado médico (audio) en un informe estructurado basado en plantilla, en una sola llamada. Hay dos formas de integrarse:
- Widget embebible — agregá un tag
<script>a tu página. El médico graba desde un botón flotante, el informe se devuelve y puede autoinsertarse en tu editor. - API directa — llamá a
POST https://api.omniai.com.ar/api/omni-aicon el archivo de audio y manejá la respuesta vos mismo.
Ambas vías usan el mismo endpoint omni-ai por detrás. Empezá por el Widget salvo que necesites control total de la interfaz de grabación.
Autenticación
Cada partner de integración recibe un medical_center_id (provisto por Omni). El ID del centro identifica las plantillas, el idioma y el estilo de informe usados para generar el resultado. Hoy no se requiere bearer token.
Escribí a [email protected] para solicitar un ID de centro.
Widget
Embeber
<script src="https://api.omniai.com.ar/static/widget/v1/omni-widget.min.js"
data-center-id="YOUR_CENTER_ID"
async></script>Se inyecta un botón de grabación flotante abajo a la derecha. El médico lo toca, dicta, frena, y el widget muestra el informe generado.
Autoinsertar el informe
El widget emite un evento omniai:report cuando el médico hace clic en "Insertar". Escuchalo y pegá el HTML (o el texto plano) en tu editor:
window.addEventListener('omniai:report', (event) => {
const { html, text } = event.detail;
// p. ej. pegar en el campo contenteditable / rich-text con foco
document.execCommand('insertHTML', false, html);
});O inicializalo manualmente con un callback:
OmniWidget.init({
centerId: 'YOUR_CENTER_ID',
onReport: ({ html, text }) => {
// Insertar en tu editor de informes
}
});Ciclo de vida del widget
| Estado | Significado |
|---|---|
| idle | Listo para grabar |
| recording | Micrófono activo |
| paused | Grabación en pausa |
| processing | Audio subido, generando informe |
| result | Informe visible en el panel |
Requisitos del navegador
- HTTPS (requerido para acceder al micrófono)
- Permiso otorgado para
navigator.mediaDevices.getUserMedia - Duración máxima de grabación: 10 minutos (configurable)
API directa
Generar un informe
POST https://api.omniai.com.ar/api/omni-ai
Content-Type: multipart/form-dataParámetros
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| file | File | Condicional | Archivo de audio (wav, mp3, m4a, ogg, flac, webm). Requerido salvo que se envíe transcription_text |
| transcription_text | String | Condicional | Texto del dictado usado en lugar del audio (ver abajo). Requerido salvo que se envíe file |
| medical_center_id | String | Sí | Identificador del centro provisto por Omni |
| user_id | String | No | Identificador del médico/usuario para analítica |
| language | String | No | Código de idioma (default spa) |
| metadata | String (JSON) | No | Metadata JSON libre, máx 10KB |
| dicom_metadata | String (JSON) | No | Metadata DICOM como fallback del estudio |
| template_html | String (HTML) | No | Plantilla preseleccionada: generar el informe contra exactamente esta plantilla (ver abajo) |
| template_name | String | No | Etiqueta de la plantilla preseleccionada; se devuelve como study_name / template_used |
| include_template | Boolean | No | Devolver el HTML de la plantilla que usó el servidor, para resaltar diferencias |
| include_rtf | Boolean | No | Devolver versión RTF de cada informe |
Nota: Sin template_html, la detección del estudio es automática a partir de la transcripción y la plantilla se matchea contra las plantillas guardadas del centro — no necesitás enviar el nombre del estudio.
Enviar texto en lugar de audio (transcription_text)
Si tu sistema ya tiene el dictado como texto (tipeado, o transcripto de tu lado), envialo como transcription_text y omití el audio — se saltea el paso de transcripción y el texto entra al pipeline exactamente donde entraría la transcripción: la detección del estudio, el matching de plantillas y la generación se comportan igual. Se combina naturalmente con template_html (sin transcripción y sin matching — el camino más rápido y totalmente determinístico).
- El audio siempre gana. Si enviás ambos, se transcribe el audio y
transcription_textse ignora por completo. No hay fallback: si el audio falla al transcribirse (p. ej. micrófono muteado) la llamada da error aunque hayas enviado texto. - Un campo
fileen blanco o untranscription_texten blanco ("" o espacios) se trata como ausente. No enviar ninguno devuelve un 400. - Texto plano, máx 50KB.
- En la respuesta:
duration_secondses0.0,speaker_countes1,language_detectedrefleja tu parámetrolanguage, ymetadata.input_sourcees"text"(vs"audio"). - La facturación no cambia — un informe generado desde texto consume crédito exactamente igual que uno generado desde audio.
Enviar tu propia plantilla (template_html)
Cuando tu sistema sabe qué plantilla debe seguir el informe (por clínica, por centro derivador, por modalidad), enviala en la llamada:
- La detección del estudio y el matching de plantillas se saltean por completo — el informe sigue tu plantilla de forma determinística.
- El resultado es siempre un único informe, aunque el dictado mencione varios estudios.
- El HTML se sanitiza del lado del servidor antes de usarse; el tamaño máximo es 100KB. HTML demasiado grande, o markup que queda vacío tras la sanitización, devuelve un 400. Un campo en blanco ("" o espacios) se trata como ausente: la llamada cae al camino normal de matching.
- HTML es el formato recomendado: preserva negritas, títulos y estructura de bloques.
- En la respuesta:
template_match_confidencees1.0ymetadata.detection_sourcees"preselected_template". - Con
include_template=true, eltemplate_htmlde la respuesta es la plantilla sanitizada que el servidor usó realmente — hacé el diff contra esa, no contra tu original.
Respuesta exitosa
{
"success": true,
"message": "Successfully processed audio and generated 1 report(s)",
"data": {
"transcription": "...",
"language_detected": "spa",
"duration_seconds": 42.1,
"speaker_count": 1,
"reports": [
{
"study_name": "IRM de Rodilla",
"template_used": "IRM de Rodilla",
"template_match_confidence": 1.0,
"report_html": "<p>...</p>",
"report_rtf": null,
"transcription_segment": "...",
"template_html": null
}
],
"processing_time_ms": 7441,
"metadata": {
"study_id": 12345,
"detection_source": "preselected_template",
"...": "..."
}
}
}Tip: Guardá data.metadata.study_id si planeás refinar este informe más adelante.
Ejemplos
cURL
curl -X POST https://api.omniai.com.ar/api/omni-ai \
-F "[email protected]" \
-F "medical_center_id=YOUR_CENTER_ID" \
-F "user_id=dr_martinez" \
-F "language=spa"cURL (plantilla preseleccionada)
curl -X POST https://api.omniai.com.ar/api/omni-ai \
-F "[email protected]" \
-F "medical_center_id=YOUR_CENTER_ID" \
-F "template_html=<h2>IRM de Rodilla</h2><p>...</p>" \
-F "template_name=IRM de Rodilla" \
-F "include_template=true"Python
import requests
url = "https://api.omniai.com.ar/api/omni-ai"
with open("audio.m4a", "rb") as f:
response = requests.post(
url,
files={"file": f},
data={
"medical_center_id": "YOUR_CENTER_ID",
"user_id": "dr_lopez",
"language": "spa",
},
)
response.raise_for_status()
result = response.json()
for report in result["data"]["reports"]:
print(report["study_name"], "->", report["report_html"])JavaScript (fetch)
async function processAudio(audioFile, centerId, userId) {
const form = new FormData();
form.append('file', audioFile);
form.append('medical_center_id', centerId);
form.append('user_id', userId);
form.append('language', 'spa');
const res = await fetch('https://api.omniai.com.ar/api/omni-ai', {
method: 'POST',
body: form,
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(err.error || `HTTP ${res.status}`);
}
const { data } = await res.json();
return data.reports[0].report_html;
}Refinar un informe
POST https://api.omniai.com.ar/api/omni-ai/refine
Content-Type: multipart/form-dataEl médico dicta una corrección sobre un informe ya generado ("cambiar menisco interno por menisco externo"). El servidor transcribe el audio, cambia solo el elemento apuntado — más la Conclusión del informe cuando el cambio la afecta — y devuelve el informe actualizado. El refinamiento nunca regenera el informe completo, y no se factura.
Parámetros
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| file | File | Condicional | Audio con la corrección dictada. Requerido salvo que se envíe transcription_text |
| transcription_text | String | Condicional | La corrección como texto en lugar de audio (máx 50KB, mismas reglas que en generación: el audio gana, blanco = ausente) |
| medical_center_id | String | Sí | Identificador del centro provisto por Omni |
| report_html | String (HTML) | Sí | El último informe generado, exactamente como lo devolvió la API (o como quedó tras el último refinamiento) |
| template_html | String (HTML) | No | La plantilla original del informe; se usa como referencia de estilo/formato de solo lectura (máx 100KB) |
| template_name | String | No | Nombre de la plantilla/tipo de estudio, habilita reglas por tipo de estudio |
| selected_html | String (HTML) | No | Elemento HTML exacto a refinar; omitilo para que el modelo ubique el objetivo desde el dictado |
| study_id | Integer | No | El metadata.study_id de la llamada de generación, para trazar el refinamiento |
| user_id | String | No | Identificador del médico/usuario para analítica |
| language | String | No | Código de idioma (default spa) |
Respuesta exitosa
{
"success": true,
"message": "Successfully refined report",
"data": {
"transcription": "cambiar menisco interno por menisco externo",
"report_html": "<p>...informe completo con la corrección aplicada...</p>",
"patch": {
"refined_text": "<p>Menisco externo sin alteraciones.</p>",
"target_html": "<p>Menisco interno sin alteraciones.</p>",
"conclusion_text": null,
"conclusion_original_text": null
},
"processing_time_ms": 3120,
"metadata": {
"medical_center_id": "your-center",
"study_id": 12345,
"event_id": "678",
"model_used": "gemini-2.5-flash",
"...": "..."
}
}
}data.report_htmles el informe completo con la corrección ya aplicada — usalo directamente.data.patchdescribe las regiones exactas que cambiaron, si preferís empalmarlas en tu propio editor: reemplazá la primera ocurrencia detarget_htmlporrefined_text, y la deconclusion_original_textporconclusion_text(cuando no sea null).- Para encadenar refinamientos, enviá el
report_htmldevuelto como entrada de la siguiente llamada de refine.
Ejemplo
curl -X POST https://api.omniai.com.ar/api/omni-ai/refine \
-F "[email protected]" \
-F "medical_center_id=YOUR_CENTER_ID" \
-F "report_html=<p>...último informe generado...</p>" \
-F "template_html=<p>...plantilla original...</p>" \
-F "study_id=12345"Respuesta de error
Los errores devuelven un status no-2xx con:
{
"success": false,
"error": "Human-readable error message",
"error_code": 400
}| Código | Causa | Solución |
|---|---|---|
| 400 | Falta la entrada (ni file ni transcription_text), formato de audio no soportado, metadata JSON malformada, o template_html demasiado grande | Enviá wav/mp3/m4a/ogg/flac/webm o transcription_text (máx 50KB); validá el JSON bajo 10KB; mantené template_html bajo 100KB |
| 403 | Centro deshabilitado / no provisionado | Contactá a Omni para habilitar tu medical_center_id |
| 500 | Falló la transcripción o la generación del informe | Reintentá con backoff; contactá a soporte si persiste |
Resaltar los cambios de la IA (diff en el cliente)
El Widget resalta lo que cambió la IA haciendo un diff entre la plantilla y el informe generado. Podés hacer lo mismo en tu interfaz con jsdiff (diff en npm) — sin soporte del servidor:
import { diffWords } from 'diff';
function highlightChanges(templateText, reportText) {
return diffWords(templateText, reportText)
.map((part) =>
part.added
? `<mark class="ai-change">${part.value}</mark>`
: part.removed
? ''
: part.value
)
.join('');
}- Línea base: pedí
include_template=trueen la generación y hacé el diff contra eltemplate_htmldevuelto. - Después de un refine, hacé el diff entre el
report_htmlanterior y el nuevo para resaltar solo la corrección. - Hacé el diff sobre el texto visible (p. ej. vía
DOMParser) en lugar de strings HTML crudos para resaltados más limpios.
Health check
Usá este endpoint para verificar conectividad desde tu backend:
curl https://api.omniai.com.ar/api/healthRespuesta:
{
"status": "healthy",
"services": {
"api": {
"service": "api",
"status": "healthy",
"available": true
}
}
}Soporte
Consultas y aprovisionamiento: [email protected]
