> ## Documentation Index
> Fetch the complete documentation index at: https://developers.mihu.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAI-compatible API

> Use the official OpenAI SDKs by changing the base URL

Use the official OpenAI SDK (or any OpenAI-compatible client) with `base_url = https://stt.eu.mihu.ai/v1` and the Umay key
as API key. OpenAI model names are recognised by **Umay v1** (`umay`, files) and, live, by **Umay Stream v1**.

```python theme={null}
from openai import OpenAI
client = OpenAI(base_url="https://stt.eu.mihu.ai/v1", api_key=KEY)

r = client.audio.transcriptions.create(model="gpt-4o-transcribe", file=open("call.wav", "rb"), language="tr")
print(r.text)                                                    # "Adresiniz Şirinyer Mahallesi, Cumhuriyet Caddesi No: 17."

# Umay options go in extra_body (sent as normal form fields)
r = client.audio.transcriptions.create(
    model="whisper-1", file=open("call.wav", "rb"), language="tr", response_format="verbose_json",
    timestamp_granularities=["word", "segment"],
    extra_body={"gender_detection": True, "diarize": True, "hint_context": "izmir", "normalize": True, "mode": "normal"})
print(r.segments[0].start, r.segments[0].text, r.to_dict().get("speakers"))

# speakers in OpenAI's diarized format
r = client.audio.transcriptions.create(model="gpt-4o-transcribe-diarize", file=open("call.wav", "rb"),
                                       response_format="diarized_json")
for s in r.segments:
    print(s.speaker, s.start, s.end, s.text)                     # A 0.4 3.1 ...

# server-sent events
for ev in client.audio.transcriptions.create(model="gpt-4o-transcribe", file=open("call.wav", "rb"), stream=True):
    print(ev.type, getattr(ev, "delta", None) or getattr(ev, "text", ""))
```

```bash theme={null}
curl https://stt.eu.mihu.ai/v1/audio/transcriptions -H "Authorization: Bearer $KEY" \
  -F file=@call.wav -F model=whisper-1 -F response_format=srt -F hint_context=mugla
```

## File: `POST /v1/audio/transcriptions` with an OpenAI model name

| OpenAI field | Umay behaviour |
| - | - |
| `file` | Any common format, resampled to 16 kHz (as native) |
| `model` | `whisper-1`, `gpt-4o-transcribe`, `gpt-4o-mini-transcribe`, `gpt-transcribe` → Umay v1; `gpt-4o-transcribe-diarize` → Umay v1 with diarization on |
| `language` | ISO-639-1 (`tr`, also `tr-TR`, `turkish`). Not sent: Turkish where the server has it, else its only language (no language detection). Unknown → `400` |
| `languages[]` | Used only when `language` is not sent: the first one this server has |
| `prompt` | Turned into hints (Turkish; other languages ignore it): a list (`"Bornova, Karşıyaka, Ali Yılmaz"`) gives each item of up to four words as a phrase; in running text only names count — runs of capitalised words not at a sentence start, as a whole and word by word (`"Müşterimiz Ayşe Demir Bornova'da oturuyor."` → `Ayşe Demir Bornova`, `Ayşe`, `Demir`, `Bornova`); a suffix after an apostrophe is dropped. Style instructions have no effect |
| `keywords[]` | Each keyword is a hint phrase (Turkish; other languages ignore them) |
| `response_format` | `json` (default), `text`, `srt`, `vtt`, `verbose_json`, `diarized_json` — see below. Other values → `400` |
| `timestamp_granularities[]` | `segment` and/or `word` (with `verbose_json`); word times come from the recogniser (first-token time of each word; end ≈ last token). `word` together with `voice_isolation` → `400` |
| `stream` | `true` → server-sent events (also for `whisper-1`) |
| `temperature`, `include[]`, `chunking_strategy`, `known_speaker_names[]`, `known_speaker_references[]` | Accepted and ignored (no log-probabilities; speakers are labelled `A`, `B`, … in order of appearance) |
| Umay fields (`extra_body`) | `normalize`, `dialect_correction`, `place_correction`, `abbreviations`, `mode`, `gender_detection`, `diarize`, `max_speakers`, `speaker_lock`, `voice_isolation`, `hints`, `hint_context`, `hints_score` (and the pre-1.0.9 names) work exactly as in the native API; defaults as native (`normalize=true`, `mode=normal`) |

Answer formats (`text` always has Damga Pro ITN unless `normalize=false`; usage is the audio duration):

| `response_format` | Answer |
| - | - |
| `json` | `{"text": "...", "usage": {"type": "duration", "seconds": 7.2}}`; with voice options also `"speakers"` / `"segments"` (as native, speakers `A`, `B`, …) |
| `text` | the text, `text/plain` |
| `srt`, `vtt` | subtitles; cues split at pauses ≥ 0.6 s, after 6 s / 80 characters or at a sentence end; ITN per cue; with `diarize` the speaker as a WebVTT voice tag (`<v A>`) |
| `verbose_json` | `{"task": "transcribe", "language": "turkish", "duration", "text", "segments": [{"id", "seek", "start", "end", "text", "tokens", "temperature", "avg_logprob", "compression_ratio", "no_speech_prob"}], "words": [{"word", "start", "end"}], "usage"}` — only `id`, `start`, `end`, `text` carry information; `words` are the recogniser's words (spoken form where ITN changed the text) |
| `diarized_json` | `{"task": "transcribe", "duration", "text", "segments": [{"type": "transcript.text.segment", "id": "seg_0", "start", "end", "text", "speaker": "A"}], "usage"}`; switches diarization on |

**Streaming** (`stream=true`, `text/event-stream`): `transcript.text.delta` events (`{"type", "delta"}`) as parts of the
file are decoded — a file is decoded in parts of up to 28 s, so a short file gives one delta — then
`transcript.text.done` (`{"type", "text", "usage"}`, same text as `json`). With voice options the file is analysed first,
then sent segment by segment; with diarization each delta has `segment_id` and is followed by a `transcript.text.segment`
event (`id`, `start`, `end`, `text`, `speaker`). Errors after the stream started come as an event `{"type": "error", "error": {...}}`.

**Errors** for OpenAI model names use OpenAI's shape (`400` also for invalid field values, where the native API answers `422`):

```json theme={null}
{"error": {"message": "unknown hint_context 'x' for language 'tr'", "type": "invalid_request_error", "param": "hint_context", "code": null}}
```

`503` (busy) has `"type": "server_error"`; `GET /v1/models` lists `umay`, `umay-stream` and the accepted OpenAI names.

## Live: `WS /v1/realtime` (OpenAI Realtime transcription session)

```
wss://stt.eu.mihu.ai/v1/realtime?intent=transcription          (key: Authorization header, ?api_key=, or subprotocol)
```

Both the current session format (`session.update` with `"type": "transcription"`) and the beta one
(`transcription_session.update`) are accepted; the answer uses the same family. `/v1/stream` is unchanged.

**Client → server**

| Event | Umay behaviour |
| - | - |
| `session.update` / `transcription_session.update` | `audio.input.format` (`{"type": "audio/pcm", "rate": 24000}` default, any rate 8–48 kHz; `audio/pcmu`, `audio/pcma` = G.711 8 kHz) or beta `input_audio_format` (`pcm16` = 24 kHz, `g711_ulaw`, `g711_alaw`); `transcription.language` (default as files), `transcription.prompt` / `keywords` (→ hints, as files), `transcription.model` (any; recognition is always Umay Stream v1). Umay options (`normalize`, `dialect_correction`, `place_correction`, `abbreviations`, `gender_detection`, `diarize`, `speaker_lock`, `voice_isolation`, `max_speakers`, `hints`, `hint_context`, `hints_score`) inside `transcription` or the session object, or in the URL query. Language, prompt, keywords and Umay options can only change before the first audio |
| `input_audio_buffer.append` | `audio`: base64 audio in the session format; resampled to 16 kHz. Binary frames in the session format are accepted too |
| `input_audio_buffer.commit` | Ends the current turn now (final text without waiting for the pause); less than 100 ms of audio since the last turn → error `input_audio_buffer_commit_empty`. A turn without recognised words gives no events |
| `input_audio_buffer.clear` | Answered with `input_audio_buffer.cleared` (audio already decoded is not taken back) |
| `mihu.end` (Umay) | Last turn, `mihu.usage`, then the server closes. Closing the socket also ends the session |

Turn detection is always the server's: a turn ends after \~0.8 s of silence (as `/v1/stream`); `turn_detection` and
`noise_reduction` settings are accepted and echoed but do not change it (`voice_isolation` removes other voices).

**Server → client** (per turn, in this order)

| Event | Meaning |
| - | - |
| `session.created` / `session.updated` (`transcription_session.updated` for beta) | Session settings |
| `input_audio_buffer.speech_started` | First words of a turn recognised (`audio_start_ms`, `item_id`) |
| `conversation.item.input_audio_transcription.delta` | New words while the person speaks (`delta`, spoken form, no ITN) |
| `input_audio_buffer.speech_stopped`, `input_audio_buffer.committed`, `conversation.item.added` (beta: `conversation.item.created`) | Turn ended (`audio_end_ms`, `item_id`, `previous_item_id`) |
| `conversation.item.input_audio_transcription.completed` | Final text of the turn (`transcript`, ITN applied), `usage` = turn duration |
| `mihu.speaker` | `gender_detection`: `{"speaker": "S1", "gender": "female", "percent": 97, "item_id"}` (\~2 s after speech starts, updated) |
| `mihu.item.speaker` | `diarize` / `speaker_lock`: speaker of the turn, sent just before its `completed` |
| `mihu.ignored` | `speaker_lock`: another voice's turn (`transcript`); its `completed` has an empty `transcript` |
| `mihu.usage` | At the end: `audio_seconds` |
| `error` | `{"type": "error", "error": {"type", "code", "message", "param", "event_id"}}` — e.g. `unsupported_event` for `response.create`; busy → `server_busy` and close `1013` |

```python theme={null}
import base64
from openai import OpenAI
client = OpenAI(base_url="https://stt.eu.mihu.ai/v1", api_key=KEY)
with client.realtime.connect(extra_query={"intent": "transcription"}) as conn:
    conn.session.update(session={"type": "transcription", "audio": {"input": {
        "format": {"type": "audio/pcm", "rate": 24000},
        "transcription": {"model": "gpt-4o-transcribe", "language": "tr", "gender_detection": True}}}})
    for chunk in pcm24k_chunks:                                   # 100 ms of 24 kHz mono int16 PCM each
        conn.input_audio_buffer.append(audio=base64.b64encode(chunk).decode())
    conn.send({"type": "mihu.end"})
    for ev in conn:
        if ev.type == "conversation.item.input_audio_transcription.completed":
            print(ev.transcript)
        elif ev.type == "mihu.speaker":
            print(ev.to_dict())
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.