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.
https://api.omi.health/v1/audio/transcriptionsDirect upload: 200 inline or 202 job
https://api.omi.health/v1/jobsCreate upload slot
/v1/jobs/{job_id}/completeFreeze upload and enqueue
/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
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
| Field | Default | Notes |
|---|---|---|
model | omi-medical-1 | Only the flagship is available on this route. |
filename | required | Must match the declared content type. |
content_type | required | Supported audio MIME type. |
content_length_bytes | required | Validated before an upload slot is created. |
language | key default, then en | Explicit supported tag. Auto is invite-only for long jobs. |
vocabulary | none | Array of up to 1,000 terms; Arabic/Hindi and auto accept up to 30. |
patterns | none | Request-scoped preview field. |
diarize | false | Attach speaker labels and word timestamps to the sealed record. |
max_speakers | 4 | Integer 1–4. |
webhook_url | none | HTTPS 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
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.