# Batch STT API

`POST /stt/transcribe` transcribes an uploaded audio file — a whole recording at once, not a live stream. Same API key, same balance as your voice agents. **No agent required** — you pick the STT provider and model directly.

## Request

```
POST /stt/transcribe
Content-Type: multipart/form-data
```

| field | type | notes |
|---|---|---|
| `file` | file | **required** — `mp3`, `wav`, `m4a`, or `webm`. Anything else → `415` |
| `provider` | form field | `groq` (default), `voxtral`, `elevenlabs`, `deepgram`. Unknown → `422` |
| `model` | form field | optional — defaults to the provider's primary model |
| `language` | form field | `en` or `fr`. Omit for auto-detect. Other values → `422` |
| `diarize` | form field | `true` to label each segment with a speaker — see below |
| `timestamp_granularities` | form field | `segment` (default) or `word` |

Size and length limits: uploads over the configured maximum return `413 file_too_large`; audio longer than the configured maximum decoded duration returns `413 audio_too_long`.

```bash
curl -X POST https://api.wakili.dev/stt/transcribe \
  -H "Authorization: Bearer wak_your_api_key" \
  -F provider=deepgram \
  -F language=en \
  -F diarize=true \
  -F file=@call.m4a
```

## Response

```json
{
  "text": "Hi, I'd like to move my appointment. Sure, what day works?",
  "language": "en",
  "duration_seconds": 74.2,
  "provider": "deepgram",
  "model": "nova-3",
  "segments": [
    { "start": 0.0, "end": 3.1, "text": "Hi, I'd like to move my appointment.", "speaker_id": "speaker_0" },
    { "start": 3.4, "end": 5.9, "text": "Sure, what day works?", "speaker_id": "speaker_1" }
  ],
  "upstream_cost_usd": 0.0053,
  "cost_usd": 0.0058
}
```

`speaker_id` is `null` unless `diarize=true`.

## Diarization

`diarize=true` is only honoured for providers that support speaker labelling: **`deepgram`**, **`elevenlabs`**, or **`voxtral`**. With any other provider (e.g. `groq`) the request returns `422` with `code: "diarization_unsupported"` rather than silently ignoring the flag.

## Billing

You pay the provider's **list price** for the audio duration, plus a flat **10% routing fee**:

```
cost_usd = upstream_cost_usd × 1.10
```

`upstream_cost_usd` is the per-audio-minute list price for the chosen provider. Debited from the same balance as calls. `402` with `code: "insufficient_balance"` if the balance can't cover it (checked before the provider is called).

## Errors

| status | when |
|---|---|
| `401` | missing/invalid API key |
| `402` | insufficient balance |
| `413` | file too large / audio too long |
| `415` | unsupported audio format |
| `422` | unknown `provider`, bad `language`/`timestamp_granularities`, or `diarization_unsupported` |
| `502` | upstream STT provider error (`code: "provider_error"`) |
