Omnimed AI

    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:

    1. 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.
    2. API directa — llamá a POST https://api.omniai.com.ar/api/omni-ai con 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

    EstadoSignificado
    idleListo para grabar
    recordingMicrófono activo
    pausedGrabación en pausa
    processingAudio subido, generando informe
    resultInforme 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-data

    Parámetros

    ParámetroTipoRequeridoDescripción
    fileFileCondicionalArchivo de audio (wav, mp3, m4a, ogg, flac, webm). Requerido salvo que se envíe transcription_text
    transcription_textStringCondicionalTexto del dictado usado en lugar del audio (ver abajo). Requerido salvo que se envíe file
    medical_center_idStringSíIdentificador del centro provisto por Omni
    user_idStringNoIdentificador del médico/usuario para analítica
    languageStringNoCódigo de idioma (default spa)
    metadataString (JSON)NoMetadata JSON libre, máx 10KB
    dicom_metadataString (JSON)NoMetadata DICOM como fallback del estudio
    template_htmlString (HTML)NoPlantilla preseleccionada: generar el informe contra exactamente esta plantilla (ver abajo)
    template_nameStringNoEtiqueta de la plantilla preseleccionada; se devuelve como study_name / template_used
    include_templateBooleanNoDevolver el HTML de la plantilla que usó el servidor, para resaltar diferencias
    include_rtfBooleanNoDevolver 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_text se 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 file en blanco o un transcription_text en blanco ("" o espacios) se trata como ausente. No enviar ninguno devuelve un 400.
    • Texto plano, máx 50KB.
    • En la respuesta: duration_seconds es 0.0, speaker_count es 1, language_detected refleja tu parámetro language, y metadata.input_source es "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_confidence es 1.0 y metadata.detection_source es "preselected_template".
    • Con include_template=true, el template_html de 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-data

    El 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ámetroTipoRequeridoDescripción
    fileFileCondicionalAudio con la corrección dictada. Requerido salvo que se envíe transcription_text
    transcription_textStringCondicionalLa corrección como texto en lugar de audio (máx 50KB, mismas reglas que en generación: el audio gana, blanco = ausente)
    medical_center_idStringSíIdentificador del centro provisto por Omni
    report_htmlString (HTML)SíEl último informe generado, exactamente como lo devolvió la API (o como quedó tras el último refinamiento)
    template_htmlString (HTML)NoLa plantilla original del informe; se usa como referencia de estilo/formato de solo lectura (máx 100KB)
    template_nameStringNoNombre de la plantilla/tipo de estudio, habilita reglas por tipo de estudio
    selected_htmlString (HTML)NoElemento HTML exacto a refinar; omitilo para que el modelo ubique el objetivo desde el dictado
    study_idIntegerNoEl metadata.study_id de la llamada de generación, para trazar el refinamiento
    user_idStringNoIdentificador del médico/usuario para analítica
    languageStringNoCó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_html es el informe completo con la corrección ya aplicada — usalo directamente.
    • data.patch describe las regiones exactas que cambiaron, si preferís empalmarlas en tu propio editor: reemplazá la primera ocurrencia de target_html por refined_text, y la de conclusion_original_text por conclusion_text (cuando no sea null).
    • Para encadenar refinamientos, enviá el report_html devuelto 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ódigoCausaSolución
    400Falta la entrada (ni file ni transcription_text), formato de audio no soportado, metadata JSON malformada, o template_html demasiado grandeEnviá wav/mp3/m4a/ogg/flac/webm o transcription_text (máx 50KB); validá el JSON bajo 10KB; mantené template_html bajo 100KB
    403Centro deshabilitado / no provisionadoContactá a Omni para habilitar tu medical_center_id
    500Falló la transcripción o la generación del informeReintentá 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=true en la generación y hacé el diff contra el template_html devuelto.
    • Después de un refine, hacé el diff entre el report_html anterior 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/health

    Respuesta:

    {
      "status": "healthy",
      "services": {
        "api": {
          "service": "api",
          "status": "healthy",
          "available": true
        }
      }
    }

    Soporte

    Consultas y aprovisionamiento: [email protected]