Core workflow

Asynchronous transcription

Omi provides one direct-upload front door plus the presigned large-upload path. Direct uploads dispatch automatically; presigned uploads keep files above 100,000,000 bytes out of your API process.

POSThttps://api.omi.health/v1/audio/transcriptions

Direct upload: 200 inline or 202 job

POSThttps://api.omi.health/v1/jobs

Create upload slot

POST/v1/jobs/{job_id}/complete

Freeze upload and enqueue

GET/v1/jobs/{job_id}

Poll status and result

The direct-upload front door

Send the same multipart request to /v1/audio/transcriptions for files up to exactly 100,000,000 bytes. Audio under 30.000 seconds without a callback returns an OpenAI-compatible 200 response. Audio at or above 30.000 seconds—or any request with webhook_url—returns a 202 job envelope with Location and Retry-After: 5. Files of 30 seconds or longer use the asynchronous-optimized pipeline.

Use presigned upload for larger files

The 100 MB direct cap covers typical 16 kHz mono clinical audio; it does not cover every 30-minute high-rate or multichannel file. Use the presigned path below for files up to 1 GiB.

1. Create a job

curl https://api.omi.health/v1/jobs \
  -H "Authorization: Bearer $OMI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "omi-medical-1",
    "filename": "consultation.flac",
    "content_type": "audio/flac",
    "content_length_bytes": 1234567,
    "language": "en",
    "vocabulary": ["Hepcludex", "Tinel"],
    "diarize": true,
    "max_speakers": 2
  }'

The response contains a presigned POST form in upload, plus owner-scoped complete_url and poll_url. Send every returned upload field exactly as provided.

2. Upload, complete, and poll

# Install once: python -m pip install requests
import os, time, requests

api = "https://api.omi.health"
headers = {"Authorization": f"Bearer {os.environ['OMI_API_KEY']}"}
audio_path = "consultation.flac"
size = os.path.getsize(audio_path)

# 1. Reserve an owner-scoped upload.
job = requests.post(
    f"{api}/v1/jobs",
    headers=headers,
    json={
        "model": "omi-medical-1",
        "filename": "consultation.flac",
        "content_type": "audio/flac",
        "content_length_bytes": size,
        "language": "en",
    },
).json()

# 2. Upload directly to signed storage.
with open(audio_path, "rb") as audio:
    upload = requests.post(
        job["upload"]["url"],
        data=job["upload"]["fields"],
        files={"file": ("consultation.flac", audio, "audio/flac")},
    )
upload.raise_for_status()

# 3. Freeze the upload and enqueue transcription.
requests.post(job["complete_url"], headers=headers).raise_for_status()

# 4. Poll with bounded backoff.
delay = 1
while True:
    state = requests.get(job["poll_url"], headers=headers).json()
    if state["status"] == "succeeded":
        result = requests.get(state["result"]["download_url"]).json()
        print(result["text"])
        break
    if state["status"] == "failed":
        raise RuntimeError(state["error"]["message"])
    time.sleep(delay)
    delay = min(delay * 1.5, 10)

The state machine is awaiting_upload → accepted → running → succeeded, with failed as the terminal error state. Poll with bounded exponential backoff and jitter.

Job request fields

FieldDefaultNotes
modelomi-medical-1Only the flagship is available on this route.
filenamerequiredMust match the declared content type.
content_typerequiredSupported audio MIME type.
content_length_bytesrequiredValidated before an upload slot is created.
languagekey default, then enExplicit supported tag. Auto is invite-only for long jobs.
vocabularynoneArray of up to 1,000 terms; Arabic/Hindi and auto accept up to 30.
patternsnoneRequest-scoped preview field.
diarizefalseAttach speaker labels and word timestamps to the sealed record.
max_speakers4Integer 1–4.
webhook_urlnoneHTTPS callback on port 443; requires a separate signing secret.

Retention and result access

  • Job audio, results, and metadata are retained for 72 hours.
  • Uploads and results are owner-scoped; another API key cannot poll your job.
  • Downloaded result URLs are signed and expire after 15 minutes.
  • Each successful poll may mint a fresh 15-minute result URL while the result is retained.
  • After retention, polling returns result: {"expired": true}. An already-expired signed URL returns the storage provider’s native expiry response.
  • Do not reuse a presigned form or upload a different file into an existing job.

Automatic language on long audio is invited

Public automatic routing is currently synchronous only. Async language: "auto" is enabled for named invited testers while Omi keeps long multilingual work isolated from short-request latency. Explicit-language jobs are public and unchanged.

Idempotent job creation

Send an Idempotency-Key of 1–255 characters when creating an async job. Keys are scoped to the API credential and retained for 24 hours. Repeating the same request returns the original job; reusing the key for different audio or options returns idempotency_conflict. On an inline 200 request, the header is accepted and ignored.