---
title: Quickstart
description: Generate Arabic speech with cURL or the OpenAI Python SDK and save a playable WAV.
---

## 1. Create an API key

Open [API Keys](/dashboard/api-keys), click **Create key**, and copy the key. Configure `SAWTAK_API_KEY` using the [authentication setup](/docs/authentication#configure-the-key-locally).

## 2. Choose a voice

```bash
curl --fail --show-error https://api.sawtakarabi.ai/v1/voices \
  -H "Authorization: Bearer $SAWTAK_API_KEY"
```

Choose an entry in `data` with `status: "ready"` and copy its `id`. See [Voices](/docs/voices) for dialect filters and audio previews.

## 3. Generate speech

Speech generation uses your Sawtak Arabi balance. Replace `<VOICE_ID>` with the ID you selected. To save a playable file with completion checks, use the [Python example below](#python-save-a-playable-wav).

The cURL example receives a WAV stream and saves its response headers:

```bash
curl --fail --show-error https://api.sawtakarabi.ai/v1/audio/speech \
  -H "Authorization: Bearer $SAWTAK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "voice": "<VOICE_ID>",
    "input": "مرحباً بك",
    "response_format": "wav"
  }' \
  --dump-header speech.headers \
  --output speech.partial.wav
```

Keep the `X-Request-Id` in `speech.headers`. Before using the audio, [check the operation](/docs/operations#inspect-an-operation) for `outcome: "completed"` and confirm its `completion_bytes` matches the saved PCM payload, excluding the WAV header. Then [finalize the file’s length fields](/docs/streaming#playing-streaming-wav-audiowav) for standard media players. Finalizing a WAV does not establish that generation completed.

See [Text to speech](/docs/text-to-speech) for optional settings and [Errors](/docs/errors) if a request fails. Using Claude Code or Codex? The [official skill](/docs/agent-skill) handles voice selection and creates a finalized WAV for you.

## Python: save a playable WAV

Use the [official OpenAI Python SDK](https://github.com/openai/openai-python) with Sawtak Arabi's base URL and your Sawtak Arabi API key. The SDK is the client in this example; speech is generated by Sawtak Arabi's model. This example requires Python 3.10+ and uses Python's built-in `wave` module to finalize the file, so FFmpeg is unnecessary.

```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'openai>=1.102.0,<3'
export SAWTAK_VOICE_ID='<VOICE_ID>'
```

Replace `<VOICE_ID>` with the ready voice selected above. Save this as `speech.py` and run `python speech.py` in the terminal where you configured `SAWTAK_API_KEY`. Run it on your server or computer; keep the key out of browser code. Generation uses your Sawtak Arabi balance.

```python
import os
import time
import uuid
import wave
from pathlib import Path
from urllib.parse import quote

from openai import OpenAI

output = Path("speech.wav")
partial = Path("speech.partial.wav")
if output.exists() or partial.exists():
    raise SystemExit("Reuse the existing audio or choose new output filenames.")

with OpenAI(
    api_key=os.environ["SAWTAK_API_KEY"],
    base_url="https://api.sawtakarabi.ai/v1",
    max_retries=0,
    timeout=180.0,
) as client:
    with partial.open("xb") as file, wave.open(file, "wb") as wav:
        wav.setparams((1, 2, 24000, 0, "NONE", "not compressed"))
        received = 0
        pending = b""
        with client.audio.speech.with_streaming_response.create(
            model="arabic-tts-1",
            voice=os.environ["SAWTAK_VOICE_ID"],
            input="مرحباً بك في صوتك عربي.",
            response_format="pcm",
            extra_body={"sample_rate": 24000},
            extra_headers={"Idempotency-Key": str(uuid.uuid4())},
        ) as response:
            operation_id = response.headers.get("x-request-id")
            print("Operation:", operation_id, flush=True)
            if (
                response.headers.get("content-type", "").split(";")[0] != "audio/pcm"
                or response.headers.get("x-sample-rate") != "24000"
                or response.headers.get("x-channels") != "1"
            ):
                raise RuntimeError("Unexpected audio format; inspect the operation.")
            for chunk in response.iter_bytes():
                pending += chunk
                size = len(pending) - len(pending) % 2
                wav.writeframes(pending[:size])
                received += size
                pending = pending[size:]

    if not received or pending or not operation_id:
        raise RuntimeError("Incomplete audio or missing operation ID; keep the partial file.")
    for attempt in range(5):
        operation = client.get(
            "/operations/" + quote(operation_id, safe=""), cast_to=object
        )
        if operation["state"] == "terminal":
            break
        time.sleep(1)
    if operation.get("outcome") != "completed" or operation.get("completion_bytes") != received:
        raise RuntimeError("Completion unconfirmed; keep the partial file and inspect the operation.")

os.link(partial, output)
partial.unlink()
print(f"Saved {output}: {received / (24000 * 2):.2f} seconds")
```

The SDK streams raw PCM; `wave` writes a standard mono, 16-bit, 24 kHz WAV. The script handles chunks that split an audio sample and promotes the partial file only after the operation reports completion with the expected byte count. It refuses to replace existing audio and disables automatic speech retries.

If it stops, keep `speech.partial.wav` and the printed operation ID. Follow [Operations and retries](/docs/operations#inspect-an-operation) before submitting again: a partial recording may already be charged, and another run starts a new request. A pending or unavailable status does not prove failure. For narration workflows with saved request metadata and recovery commands, use the [official skill](/docs/agent-skill).

Pronunciation enhancement is off by default. To enable it, add `"enhance_pronunciation": True` to `extra_body`; it is separate from the default text normalization. Choose a voice for your intended dialect and listen to a short representative script before integrating longer content. See [text preparation](/docs/text-to-speech#text-preparation) and [streaming playback](/docs/streaming#playing-raw-pcm) for the next steps.
