---
name: nabrah-api
title: Nabrah Voice AI API — Integration Skill for AI Agents
description: >-
  Machine-readable integration guide for the Nabrah Voice AI REST API.
  Nabrah is a Saudi-native Arabic voice AI platform: text-to-speech (TTS),
  speech-to-text (STT), voice cloning, autonomous voice agents (inbound &
  outbound calls, SIP/telephony), WhatsApp & omnichannel inbox, tickets, and
  contacts. Use this file to have an AI coding agent build a working
  integration end to end.
version: "2026-08-13"
provider: Nabrah AI
homepage: https://www.nabrah.ai
docs: https://docs.nabrah.ai
dashboard: https://app.nabrah.ai
api_base_url: https://api.nabrah.ai/api
api_ext_base_url: https://api.nabrah.ai/api/ext
openapi: https://api.nabrah.ai/api/openapi.json
authentication:
  type: apiKey
  in: header
  name: X-API-Key
  value_prefix: "nb_"
  how_to_obtain: "Dashboard → Developers tab (https://app.nabrah.ai). One key is bound to exactly one project/workspace."
  validate_endpoint: "GET https://api.nabrah.ai/api/ext/test"
contact: support@nabrah.ai
license: proprietary
keywords:
  - Nabrah
  - نبرة
  - Arabic voice AI
  - Saudi Arabic text to speech
  - Arabic TTS API
  - Arabic STT API
  - Arabic speech recognition
  - voice cloning API
  - AI voice agent
  - AI call center API
  - outbound calling API
  - SIP trunk API
  - WhatsApp Business API agent
  - omnichannel inbox API
  - ElevenLabs alternative Arabic
  - Google Cloud TTS alternative Arabic
audience:
  - AI coding agents
  - LLM agents
  - developers integrating voice AI
---

# Nabrah Voice AI API — AI Agent Integration Skill

> **What this file is.** A single, self-contained guide an AI coding agent (Claude Code, Cursor,
> Copilot, custom LLM agents, etc.) can read to integrate an application with **Nabrah**, the
> Saudi-native Arabic voice AI platform. It covers authentication, conventions, the full endpoint
> catalog (99 endpoints), and copy-paste request/response examples for every major flow: TTS,
> STT, voice cloning, voice agents, outbound/web calls, SIP telephony, tools, and the omnichannel
> (Omni v2) inbox — conversations, WhatsApp, tickets, and contacts.
>
> **Canonical URL:** `https://www.nabrah.ai/.well-known/agent-skills/SKILL.md`
> **Machine index:** `https://www.nabrah.ai/.well-known/agent-skills/index.json`

If you are an AI agent asked to "integrate Nabrah", "add Arabic text-to-speech", "build an AI
call agent", "transcribe Arabic audio", or "connect WhatsApp/omnichannel", follow this document.

---

## 1. Base URL & authentication

- **API base:** `https://api.nabrah.ai/api`
- **Public ("external") API base:** `https://api.nabrah.ai/api/ext`
  Every endpoint in this skill lives under `…/api/ext/…` and is authenticated **only** with an API key.
- **Auth header (required on every request):**
  ```
  X-API-Key: nb_xxxxxxxxxxxxxxxxxxxxxxxxxxxx
  ```
- **Get a key:** dashboard → **Developers** tab at `https://app.nabrah.ai`. A key is prefixed
  `nb_` and is **bound to exactly one project/workspace** — you never pass a `project_id`; the
  project is derived from the key.
- **Validate a key** (do this first): `GET /api/ext/test` → `200` means the key is valid and tells
  you which project it belongs to.
- **Never expose the key in a browser/client bundle.** Keep it server-side (env var / secret
  manager). Treat it like a password; rotate it in the Developers tab if leaked.

```bash
curl -s https://api.nabrah.ai/api/ext/test \
  -H "X-API-Key: nb_your_key_here"
```

### Minimal clients

```javascript
// Node.js (fetch, built-in on Node 18+)
const NABRAH = "https://api.nabrah.ai/api/ext";
const KEY = process.env.NABRAH_API_KEY; // nb_...

async function nabrah(path, { method = "GET", body, headers } = {}) {
  const res = await fetch(`${NABRAH}${path}`, {
    method,
    headers: {
      "X-API-Key": KEY,
      ...(body ? { "Content-Type": "application/json" } : {}),
      ...headers,
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const ct = res.headers.get("content-type") || "";
  return ct.includes("application/json") ? res.json() : res;
}

await nabrah("/test"); // validate key
```

```python
# Python (requests)
import os, requests

NABRAH = "https://api.nabrah.ai/api/ext"
KEY = os.environ["NABRAH_API_KEY"]  # nb_...

def nabrah(path, method="GET", json=None, **kw):
    r = requests.request(method, f"{NABRAH}{path}",
                         headers={"X-API-Key": KEY}, json=json, **kw)
    r.raise_for_status()
    return r.json() if "application/json" in r.headers.get("content-type", "") else r

nabrah("/test")  # validate key
```

---

## 2. Conventions

- **Transport:** HTTPS only. JSON in, JSON out — except audio endpoints (TTS returns audio bytes;
  STT and voice cloning take `multipart/form-data` file uploads).
- **IDs:** Most resources use **UUID** strings (`agent_id`, `call_id`, `voice`/voice id, `tool_id`,
  `sip_id`, analysis `id`). **Omni v2 contacts, conversations, and tickets use numeric integer
  `id`s** — don't assume UUID there.
- **Timestamps:** ISO-8601 UTC (e.g. `2026-01-15T12:00:00.000Z`). Some Omni endpoints return Unix
  epoch seconds (integers) — noted where relevant.
- **Pagination differs by area:**
  - **Calls:** `POST /api/ext/call/search` takes `limit` + `offset` in the JSON body; response has
    `total_count`.
  - **Omni v2:** list endpoints take `page` (and often `q`, `status`, `assignee_type`) as **query
    params**; responses wrap data in `{ "meta": {...}, "payload": [...] }`.
  - **STT jobs:** `GET /api/ext/stt/jobs?status=…`.
- **Errors (standard FastAPI shape):**
  | Status | Meaning | Body |
  |---|---|---|
  | `200`/`201` | Success | resource JSON or audio bytes |
  | `401` / `403` | Missing/invalid/mismatched API key | `{"detail": "..."}` |
  | `404` | Resource not found (wrong id / not in your project) | `{"detail": "..."}` |
  | `422` | Validation error (bad/missing fields, invalid combo) | `{"detail": [ {"loc": [...], "msg": "...", "type": "..."} ]}` |
  | `429` | Rate limited (if you burst too hard) | back off & retry with jitter |
  | `5xx` | Server error | retry idempotent reads with backoff |

  Always read the response body on failure — `detail` tells you exactly what's wrong (especially
  for `422`).
- **Retries & idempotency:** `GET`s are safe to retry. For **charging/side-effecting** POSTs
  (outbound calls, TTS generations that consume credits), do **not** blindly retry on timeout —
  first check state (e.g. `POST /api/ext/call/search`) to avoid duplicate calls.

---

## 3. Core concepts (domain model)

| Concept | What it is | Key endpoints |
|---|---|---|
| **Agent** | A voice/chat AI configuration: persona (`who_are_you`), `goal`, `steps`, `voice`, `languages`, behavior toggles, dynamic `variables`, webhooks, and linked tools/analysis groups. The thing that "talks" on a call. | `/api/ext/agent…` |
| **Tool** | A function-calling HTTP tool an agent can invoke mid-conversation (your webhook/API: url, method, params). Link it to agents. | `/api/ext/tool…` |
| **Analysis group** | A set of data points to extract/collect from calls (post-call analysis). Linked to agents. | `/api/ext/analysis…` |
| **Call** | An inbound or outbound phone call handled by an agent. Search, fetch, export, analytics, recording URL. | `/api/ext/call…`, `/api/ext/make-call` |
| **Web call** | A browser/WebRTC voice session with an agent; you get a connection `token`. | `/api/ext/web/make-web-call`, `/api/ext/make-web-call` |
| **SIP inbound/outbound** | Your own SIP trunks so calls terminate into Nabrah (inbound) or dial out via your provider (outbound). | `/api/ext/sip-inbound…`, `/api/ext/sip-outbound…` |
| **TTS + voices** | Text-to-speech generation and voice management, including your **cloned** voices. | `/api/ext/tts…` |
| **STT** | Speech-to-text: synchronous transcription and async jobs for long audio. | `/api/ext/stt…` |
| **Omni v2** | Omnichannel inbox: **contacts**, **conversations**, **tickets**, and **WhatsApp** (inboxes, chatbot adapter, analytics). | `/api/ext/omni_v2…` |

A typical **voice-agent** build order: create **tools** → create **analysis groups** → create an
**agent** (linking tools + analysis) → attach a **SIP** trunk or use `make-call`/`make-web-call` →
read results via **call** search/analytics/recording.

---

## 4. Worked examples (the flows agents build most)

### 4.1 Validate the key
```bash
curl -s https://api.nabrah.ai/api/ext/test -H "X-API-Key: $NABRAH_API_KEY"
# 200 → key valid (scoped to one project)
```

### 4.2 Create a voice agent
`POST /api/ext/agent` — full config; returns the created agent with its `agent_id`.
```bash
curl -s -X POST https://api.nabrah.ai/api/ext/agent \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "Riyadh Support Bot",
    "agent_type": "basic",
    "who_are_you": "You are a friendly support agent for ACME in Saudi Arabia.",
    "goal": "Answer billing questions and book callbacks.",
    "steps": "Greet, verify the customer, resolve or escalate.",
    "voice": "dania",
    "languages": ["ar"],
    "start_first": true,
    "first_sentence": "مرحبا، معك مساعد ACME. كيف أقدر أساعدك؟",
    "allow_interruptions": true,
    "speech_speed": 0.9,
    "tool_ids": [],
    "analysis_group_ids": []
  }'
```
Response (trimmed) includes `agent_id`, `project_id`, the echoed config, linked `tools`,
`system_tools`, and webhook callback blocks. Use `agent_id` everywhere an agent is required.
List agents with `GET /api/ext/agent`; fetch one with `GET /api/ext/agent/{agent_id}`.

> **Voice field.** `voice` on an agent can be a platform voice name (e.g. `dania`) or a voice id.
> For the TTS endpoint (§4.6) `voice` must be a **voice id** from `GET /api/ext/tts/voices`.

### 4.3 Place an outbound call
`POST /api/ext/call/make-call` (alias: `POST /api/ext/make-call`).
```bash
curl -s -X POST https://api.nabrah.ai/api/ext/call/make-call \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "agent_id": "AGENT_UUID",
    "call_from": "+9665XXXXXXXX",
    "call_to": "+9665YYYYYYYY",
    "injected_data": "Customer: Sara, invoice #1042 overdue",
    "variables": { "customer_name": "Sara", "invoice": "1042" },
    "tags": ["dunning", "batch-aug"]
  }'
# → { "call_ids": ["<uuid>"] }
```
- `variables` fills the agent's dynamic variables; `injected_data` is free-form context.
- `tags` are yours for later filtering in search/analytics.

### 4.4 Start a web (browser) call
`POST /api/ext/web/make-web-call` → returns a `token` your frontend uses to join the WebRTC session.
```bash
curl -s -X POST https://api.nabrah.ai/api/ext/web/make-web-call \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "agent_id": "AGENT_UUID", "identity": "user-123",
        "variables": { "plan": "pro" }, "extra_data": "from pricing page" }'
# → { "token": "..." }
```

### 4.5 Find & analyze calls
```bash
# Search (filter + paginate; limit/offset in body)
curl -s -X POST https://api.nabrah.ai/api/ext/call/search \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "agent_id": "AGENT_UUID", "status": "completed",
        "call_type": "outbound", "limit": 50, "offset": 0,
        "call_started_at_start": "2026-08-01T00:00:00.000Z",
        "call_started_at_end": "2026-08-31T23:59:59.000Z",
        "tags": ["dunning"] }'
# → { "calls": [ { "id", "status", "duration", "agent_name", ... } ], "total_count, limit, offset }
```
- **Aggregate analytics:** `POST /api/ext/call/analytics` (same filter body as search).
- **Export CSV:** `POST /api/ext/call/export`.
- **One call:** `GET /api/ext/call/{call_id}`.
- **Recording:** `GET /api/ext/call/get-download-link/{call_id}` → `{ "url": "<temporary signed url>" }`
  (download promptly; the link expires).

### 4.6 Text-to-speech (TTS)

**Step 1 — pick a voice id.** `GET /api/ext/tts/voices` returns public platform voices **plus your
cloned voices**. The chosen voice must have `is_ready: true`.
```bash
curl -s https://api.nabrah.ai/api/ext/tts/voices -H "X-API-Key: $NABRAH_API_KEY"
# → { "voices": [ { "id": "<uuid>", "name": "...", "is_public": true, "is_ready": true } ], "total_count": N }
```

**Step 2 — generate.** `POST /api/ext/tts/generations`. Same URL for both models; the response is
**audio bytes** (not JSON). Two models:

| Model | Behavior | Key fields |
|---|---|---|
| `nabrah_tts_v1` | `stream:false` (default) → **Nano**, one complete **MP3** (`response_format:"mp3"`). `stream:true` → **Omni**, progressive **WAV** stream (`response_format:"wav"`). | `input`, `voice`, `response_format`, `stream`, `use_cache`, `speed` (ignored while streaming) |
| `phantom_v1` | High-fidelity, **always a complete file** (never streamed); full tuning controls; you choose `response_format`. | `input`, `voice`, `speed`, `expressiveness`, `speech_smoothness`, `remove_silence`, `response_format` |

**Rules that matter (enforced server-side):**
- `nabrah_tts_v1` + `stream:true` **requires** `response_format:"wav"`. Sending `mp3` with
  `stream:true` → **HTTP 422**.
- `nabrah_tts_v1` + `stream:false` **requires** `response_format:"mp3"` (Nano path).
- While streaming, `speed` is forced to `1.0` and `apply_enhancements` must be `"none"`.

```bash
# Non-streaming MP3 (simplest — plays everywhere)
curl -s -X POST https://api.nabrah.ai/api/ext/tts/generations \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model":"nabrah_tts_v1", "voice":"VOICE_UUID",
        "input":"مرحبا، هذا اختبار للصوت من نبرة", "stream":false,
        "response_format":"mp3" }' \
  --output speech.mp3
```

```bash
# Streaming WAV (Omni) — read the body as it arrives
curl -s -X POST https://api.nabrah.ai/api/ext/tts/generations \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -H "Accept: audio/wav" \
  -d '{ "model":"nabrah_tts_v1", "voice":"VOICE_UUID",
        "input":"مرحبا كيف حالك", "stream":true,
        "response_format":"wav", "apply_enhancements":"none" }' \
  --output speech.wav
```
Streaming notes: read the response body as a stream (`response.body.getReader()` in the browser; pipe
to a file/sink on a server) — first bytes usually arrive in a few hundred ms. **WAV cannot be
appended to a MediaSource SourceBuffer**, so browsers generally buffer the streamed chunks and play
once complete. For a complete file that plays anywhere, use the non-streaming MP3 (Nano) path.

> Tip: add `-f -D -` to curl (`--fail` + dump headers) so an error response (e.g. a `422`/`500`)
> isn't silently written into your `.wav`/`.mp3` file.

### 4.7 Clone a voice
`POST /api/ext/tts/clone_voice` — `multipart/form-data` upload (an audio sample + a name). Returns
`{ "success", "message", "voice_id" }`. A new clone starts `is_ready:false`; **poll**
`GET /api/ext/tts/voices/{voice_id}` (or the list) until `is_ready:true`, then use that id as `voice`
in TTS or as an agent's voice.
```bash
curl -s -X POST https://api.nabrah.ai/api/ext/tts/clone_voice \
  -H "X-API-Key: $NABRAH_API_KEY" \
  -F "name=Brand Voice" \
  -F "file=@sample.wav"
```
Delete a clone with `DELETE /api/ext/tts/voices/{voice_id}`.

### 4.8 Speech-to-text (STT)

**Synchronous** (short audio): `POST /api/ext/stt/transcribe` — `multipart/form-data` file upload →
`{ "text", "puncuatedText", "audio_duration", "timestamps": { char/word/segment offsets } }`.
```bash
curl -s -X POST https://api.nabrah.ai/api/ext/stt/transcribe \
  -H "X-API-Key: $NABRAH_API_KEY" \
  -F "file=@recording.mp3"
```

**Async** (long audio, up to ~2h): `POST /api/ext/stt/async-transcribe` → returns a job
`{ "id", "status":"queued", ... }`. Poll `GET /api/ext/stt/jobs/{job_id}` until
`status:"completed"`, then read `result.text_result` (+ timestamps). List jobs with
`GET /api/ext/stt/jobs?status=completed`.
```bash
JOB=$(curl -s -X POST https://api.nabrah.ai/api/ext/stt/async-transcribe \
  -H "X-API-Key: $NABRAH_API_KEY" -F "file=@long-audio.mp3" | jq -r .id)
curl -s https://api.nabrah.ai/api/ext/stt/jobs/$JOB -H "X-API-Key: $NABRAH_API_KEY"
```

### 4.9 Tools (function calling)
```bash
# 1) create a tool (an HTTP endpoint the agent can call)
curl -s -X POST https://api.nabrah.ai/api/ext/tool \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name":"lookup_order", "description":"Look up an order by id",
        "url":"https://api.acme.com/orders", "method":"GET", "timeout_seconds":5,
        "parameters":[ {"name":"order_id","location":"query","parameter_type":"dynamic",
                        "data_type":"string","required":true,"description":"Order id"} ] }'
# → { "id": "<tool_uuid>", ... }

# 2) link it to an agent
curl -s -X POST https://api.nabrah.ai/api/ext/tool/link \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "tool_id":"TOOL_UUID", "agent_id":"AGENT_UUID" }'
```
Unlink with `DELETE /api/ext/tool/unlink/{link_id}`. List an agent's tools:
`GET /api/ext/tool/agent/{agent_id}`.

### 4.10 SIP telephony
```bash
# Inbound trunk (terminate external calls into Nabrah)
curl -s -X POST https://api.nabrah.ai/api/ext/sip-inbound \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name":"Main DID", "numbers":"+9665XXXXXXXX",
        "allowed_addresses":["1.2.3.4"], "auth_username":"user",
        "auth_password":"secret", "pbx_type":"lk" }'
```
List/get/delete via `GET|DELETE /api/ext/sip-inbound[/{sip_id}]`; the same shape exists for
`/api/ext/sip-outbound` (dial out through your own SIP provider).

### 4.11 Omni v2 — omnichannel inbox
The Omni v2 area is a full inbox: **contacts**, **conversations**, **tickets**, and **WhatsApp**.
Lists return `{ "meta": {...}, "payload": [...] }` and paginate with `?page=`. **Contact/ticket ids
are integers.**
```bash
# list contacts
curl -s "https://api.nabrah.ai/api/ext/omni_v2/contact?page=1" -H "X-API-Key: $NABRAH_API_KEY"

# create a ticket
curl -s -X POST https://api.nabrah.ai/api/ext/omni_v2/ticket \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "subject":"Refund request", "description":"Customer wants a refund for #1042",
        "ticket_type":"billing", "contact_phone":"+9665XXXXXXXX" }'

# reply on a ticket / conversation
curl -s -X POST https://api.nabrah.ai/api/ext/omni_v2/ticket/{id}/messages \
  -H "X-API-Key: $NABRAH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "content":"We have issued your refund." }'
```
WhatsApp: connect a number (`POST /api/ext/omni_v2/whatsapp/inboxes`), attach an AI chatbot
(`POST /api/ext/omni_v2/whatsapp/assign_chatbot`) so an agent auto-replies to inbound messages, and
pull analytics (`POST /api/ext/omni_v2/whatsapp/analytics`). See the full list in §5.

---

## 5. Full endpoint reference (all 99 endpoints)

All paths are relative to `https://api.nabrah.ai` and require `X-API-Key`. `?` marks an optional
parameter.

<!-- BEGIN GENERATED ENDPOINT REFERENCE -->
### Endpoint reference (all 99 endpoints)


#### Test

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/test` | Validate an API key and confirm which project it belongs to. |  |

#### Agent

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/agent` | Returns a brief listing of every agent in the workspace tied to your API key. |  |
| `POST` | `/api/ext/agent` | Creates a new agent in the project tied to your API key and returns the full agent configuration, including the generated `agent_id`. |  |
| `DELETE` | `/api/ext/agent/{agent_id}` | Permanently deletes the agent identified by `agent_id`. | `agent_id`(path) |
| `GET` | `/api/ext/agent/{agent_id}` | Returns the complete configuration for a single agent identified by `agent_id`, including persona, voice/language settings, behavioral toggles, dynamic variables, webhook callbacks. | `agent_id`(path) |
| `PUT` | `/api/ext/agent/{agent_id}` | Updates the agent identified by `agent_id` and returns the full updated configuration. | `agent_id`(path) |
| `GET` | `/api/ext/agent/{agent_id}/analysis_groups` | Lists all analysis groups currently linked to the agent identified by `agent_id`. | `agent_id`(path) |
| `POST` | `/api/ext/agent/{agent_id}/analysis_groups` | Links one or more existing analysis groups to the agent identified by `agent_id` in a single call. | `agent_id`(path) |

#### Analysis

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/analysis` | Returns a brief listing of every analysis group in the workspace tied to your API key. |  |
| `POST` | `/api/ext/analysis` | Creates a new analysis group together with its data collections and returns the full group, including the generated `id`. |  |
| `DELETE` | `/api/ext/analysis/{analysis_group_id}` | Permanently deletes the analysis group identified by `analysis_group_id` along with all its data collections and any links to agents. | `analysis_group_id`(path) |
| `GET` | `/api/ext/analysis/{analysis_group_id}` | Returns the full definition of a single analysis group, including all of its `data_collections` (each with name, type, description, and constraints). | `analysis_group_id`(path) |
| `PUT` | `/api/ext/analysis/{analysis_group_id}` | Updates the analysis group, replacing its name and data collections with the values provided, and returns the full updated group. | `analysis_group_id`(path) |
| `POST` | `/api/ext/analysis/{analysis_group_id}/agent` | Links a single agent to the analysis group and returns the new `link_id`. | `analysis_group_id`(path) |
| `GET` | `/api/ext/analysis/{analysis_group_id}/agents` | Lists all agents currently linked to the analysis group. | `analysis_group_id`(path) |
| `POST` | `/api/ext/analysis/{analysis_group_id}/agents` | Links multiple agents to the analysis group in a single call and returns one `link_id` per created link. | `analysis_group_id`(path) |
| `DELETE` | `/api/ext/analysis/link/{link_id}` | Removes a single agent↔analysis-group association identified by `link_id`. | `link_id`(path) |

#### Tool

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/tool` | List every tool defined in the current project. |  |
| `POST` | `/api/ext/tool` | Create a new function-calling tool in the current project. |  |
| `DELETE` | `/api/ext/tool/{tool_id}` | Permanently delete a tool from the current project. | `tool_id`(path) |
| `GET` | `/api/ext/tool/{tool_id}` | Retrieve the full record for a single tool by its id, including its url, method, timeout, and the complete list of parameters. | `tool_id`(path) |
| `PUT` | `/api/ext/tool/{tool_id}` | Update an existing tool in the current project. | `tool_id`(path) |
| `GET` | `/api/ext/tool/{tool_id}/agents` | List every agent that the given tool is linked to. | `tool_id`(path) |
| `GET` | `/api/ext/tool/agent/{agent_id}` | List every tool attached to the given agent. | `agent_id`(path) |
| `POST` | `/api/ext/tool/link` | Link a tool to an agent so the agent can call that tool during conversations. |  |
| `DELETE` | `/api/ext/tool/unlink/{link_id}` | Remove the link between a tool and an agent. | `link_id`(path) |

#### Call

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/call/{call_id}` | Retrieve the complete record for a single call by its UUID. | `call_id`(path) |
| `POST` | `/api/ext/call/analytics` | Compute aggregate analytics over historical calls that match a filter. |  |
| `POST` | `/api/ext/call/export` | Export the historical calls matching a filter as a downloadable CSV file. |  |
| `GET` | `/api/ext/call/get-download-link/{call_id}` | Generate a temporary signed URL to download a call's audio recording. | `call_id`(path) |
| `POST` | `/api/ext/call/make-call` | Create an outbound phone call. |  |
| `POST` | `/api/ext/call/search` | Search historical call records with filtering and pagination. |  |

#### Make call

| Method | Path | Purpose | Params |
|---|---|---|---|
| `POST` | `/api/ext/make-call` | Backward-compatible alias of POST /api/ext/call/make-call — creates an outbound phone call with identical request/response shapes. |  |

#### Make web call

| Method | Path | Purpose | Params |
|---|---|---|---|
| `POST` | `/api/ext/make-web-call` | Backward-compatible alias of POST /api/ext/web/make-web-call — starts a browser/WebRTC voice session and returns a connection token. |  |

#### Web

| Method | Path | Purpose | Params |
|---|---|---|---|
| `POST` | `/api/ext/web/make-web-call` | Create a web call: start a browser/WebRTC voice session with an agent and receive a connection token. |  |

#### SIP inbound

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/sip-inbound` | Lists every inbound SIP trunk configured for the project. |  |
| `POST` | `/api/ext/sip-inbound` | Creates a new inbound SIP trunk so external SIP/phone calls terminate into Nabrah and route to an agent. |  |
| `DELETE` | `/api/ext/sip-inbound/{sip_id}` | Permanently deletes a single inbound SIP trunk. | `sip_id`(path) |
| `GET` | `/api/ext/sip-inbound/{sip_id}` | Retrieves a single inbound SIP trunk by its id. | `sip_id`(path) |

#### SIP outbound

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/sip-outbound` | Lists every outbound SIP trunk configured for the project. |  |
| `POST` | `/api/ext/sip-outbound` | Creates a new outbound SIP trunk, enabling agents to place calls out through your own SIP provider. |  |
| `DELETE` | `/api/ext/sip-outbound/{sip_id}` | Permanently deletes a single outbound SIP trunk. | `sip_id`(path) |
| `GET` | `/api/ext/sip-outbound/{sip_id}` | Retrieves a single outbound SIP trunk by its id. | `sip_id`(path) |

#### Text to speech

| Method | Path | Purpose | Params |
|---|---|---|---|
| `POST` | `/api/ext/tts/clone_voice` | Clone a voice from an uploaded audio sample (multipart/form-data). |  |
| `POST` | `/api/ext/tts/generations` | Generate speech from text (OpenAI-compatible). Returns audio bytes. See §4.6 for model/stream rules. |  |
| `GET` | `/api/ext/tts/voices` | Lists every voice available to the project (public voices + your clones). |  |
| `DELETE` | `/api/ext/tts/voices/{voice_id}` | Delete a voice clone by its ID. | `voice_id`(path) |
| `GET` | `/api/ext/tts/voices/{voice_id}` | Fetch the full details of a single voice by its ID. | `voice_id`(path) |

#### Speech to text

| Method | Path | Purpose | Params |
|---|---|---|---|
| `POST` | `/api/ext/stt/async-transcribe` | Queue a long audio file (up to 2 hours) for background transcription. |  |
| `GET` | `/api/ext/stt/jobs` | List the async transcription jobs for the project. | `status`?(query) |
| `GET` | `/api/ext/stt/jobs/{job_id}` | Get one async transcription job by ID. | `job_id`(path) |
| `POST` | `/api/ext/stt/transcribe` | Transcribe an audio file to text (synchronous, multipart/form-data). |  |

#### Omni v2

| Method | Path | Purpose | Params |
|---|---|---|---|
| `GET` | `/api/ext/omni_v2/contact` | Paginated list of all contacts in the project. | `page`?(query) `sort`?(query) |
| `POST` | `/api/ext/omni_v2/contact` | Creates a new contact in the project. |  |
| `GET` | `/api/ext/omni_v2/contact/{contact_id}` | Retrieves a single contact by its numeric `contact_id`. | `contact_id`(path) |
| `PATCH` | `/api/ext/omni_v2/contact/{contact_id}` | Partial update of an existing contact. | `contact_id`(path) |
| `GET` | `/api/ext/omni_v2/contact/{contact_id}/conversations` | Lists all conversations that belong to the contact. | `contact_id`(path) |
| `GET` | `/api/ext/omni_v2/contact/search` | Searches contacts and returns a paginated list of matches. | `page`?(query) `q`?(query) `sort`?(query) |
| `GET` | `/api/ext/omni_v2/conversation/conversation` | Paginated list of conversations (inbox threads). | `assignee_type`?(query) `status_param`?(query) `q`?(query) `page`?(query) |
| `GET` | `/api/ext/omni_v2/conversation/conversation/{id}` | Full detail of a single conversation thread incl. embedded messages. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/assign` | Assigns the conversation to a workspace member (agent). | `id`(path) |
| `GET` | `/api/ext/omni_v2/conversation/conversation/{id}/attachments` | Returns every attachment shared in the conversation, as a flat list. | `id`(path) |
| `GET` | `/api/ext/omni_v2/conversation/conversation/{id}/messages` | Page of messages (cursor-based via `before`). | `id`(path) `before`?(query) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/messages` | Sends a new message into the conversation. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/messages_with_attachment` | Sends a message with a file attachment. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/toggle_priority` | Sets the priority of the conversation. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/toggle_status` | Changes the lifecycle status of the conversation. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/{id}/update_last_seen` | Marks the conversation as seen by the agent, clearing unread state. | `id`(path) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/contact/{contact_id}` | Lists conversations for a contact with optional body filter + `page`. | `contact_id`(path) `page`?(query) |
| `POST` | `/api/ext/omni_v2/conversation/conversation/filter` | Lists conversations matching a structured filter in the JSON body. | `page`?(query) |
| `GET` | `/api/ext/omni_v2/ticket` | Paginated list of support tickets with `meta` counts + `payload`. | `assignee_type`?(query) `status`?(query) `q`?(query) `page`?(query) |
| `POST` | `/api/ext/omni_v2/ticket` | Creates a new support ticket and returns the full ticket object. |  |
| `GET` | `/api/ext/omni_v2/ticket/{id}` | Fetches a single ticket by its numeric `id`. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/assign` | Assigns the ticket to a specific agent/user. | `id`(path) |
| `GET` | `/api/ext/omni_v2/ticket/{id}/messages` | Message history for a ticket + `meta`. | `id`(path) `before`?(query) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/messages` | Adds a new message to the ticket. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/messages_with_attachment` | Adds a message with file attachments. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/toggle_priority` | Sets (toggles) the priority of the ticket. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/toggle_status` | Changes the lifecycle status of the ticket. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/{id}/update_type` | Updates the category (type) of the ticket. | `id`(path) |
| `POST` | `/api/ext/omni_v2/ticket/contact/{contact_id}` | Lists tickets for a contact with optional body filter + `page`. | `contact_id`(path) `page`?(query) |
| `POST` | `/api/ext/omni_v2/ticket/filter` | Lists tickets using a structured server-side filter in the body. | `page`?(query) |
| `DELETE` | `/api/ext/omni_v2/ticket/inboxes` | Deletes the ticket inbox configuration for the project. |  |
| `GET` | `/api/ext/omni_v2/ticket/inboxes` | Lists the ticket inboxes configured for the project. |  |
| `PATCH` | `/api/ext/omni_v2/ticket/inboxes` | Updates the ticket inbox configuration. |  |
| `POST` | `/api/ext/omni_v2/ticket/inboxes` | Creates a ticket inbox and returns the created object. |  |
| `GET` | `/api/ext/omni_v2/whatsapp` | List WhatsApp conversations across all WhatsApp inboxes, with filters. | `assignee_type`?(query) `status_param`?(query) `q`?(query) `page`?(query) |
| `POST` | `/api/ext/omni_v2/whatsapp` | Create a new WhatsApp conversation for a contact. |  |
| `POST` | `/api/ext/omni_v2/whatsapp/analytics` | Aggregated WhatsApp conversation analytics for a date window. |  |
| `POST` | `/api/ext/omni_v2/whatsapp/assign_chatbot` | Attach a text chatbot adapter so an AI agent auto-replies to WhatsApp. |  |
| `POST` | `/api/ext/omni_v2/whatsapp/contact/{contact_id}` | Lists WhatsApp conversations for a contact + `page`. | `contact_id`(path) `page`?(query) |
| `POST` | `/api/ext/omni_v2/whatsapp/filter` | List WhatsApp conversations using a structured filter payload. | `page`?(query) |
| `GET` | `/api/ext/omni_v2/whatsapp/get_chatbot` | Retrieve the chatbot adapter currently assigned to the WhatsApp inbox. |  |
| `DELETE` | `/api/ext/omni_v2/whatsapp/inboxes` | Delete the project's WhatsApp inbox (disconnects the number). |  |
| `GET` | `/api/ext/omni_v2/whatsapp/inboxes` | List the WhatsApp inboxes connected to the project. |  |
| `PATCH` | `/api/ext/omni_v2/whatsapp/inboxes` | Update the WhatsApp inbox configuration (Cloud API creds, webhook token, etc.). |  |
| `POST` | `/api/ext/omni_v2/whatsapp/inboxes` | Connect a new WhatsApp number by creating a WhatsApp inbox. |  |
| `GET` | `/api/ext/omni_v2/whatsapp/inboxes/{inbox_id}` | Retrieve the full details of a single WhatsApp inbox. | `inbox_id`(path) |
| `DELETE` | `/api/ext/omni_v2/whatsapp/unassign_chatbot` | Remove the chatbot adapter from the WhatsApp inbox. |  |
<!-- END GENERATED ENDPOINT REFERENCE -->

---

## 6. Guidance for AI coding agents

When integrating Nabrah into a codebase, follow this checklist:

1. **Store the key in an env var** (`NABRAH_API_KEY`), never in client code or git. All requests
   are server-side.
2. **Validate first:** call `GET /api/ext/test` on startup or in a health check.
3. **Discover before you act:** list agents (`GET /api/ext/agent`) and voices
   (`GET /api/ext/tts/voices`) to get real ids instead of hardcoding.
4. **Handle audio correctly:** `POST /api/ext/tts/generations` returns **bytes**, not JSON. Respect
   the model/stream rules in §4.6 (mp3 for Nano/non-stream, wav for Omni/stream; `mp3`+`stream` →
   422).
5. **Poll async work:** cloned voices (`is_ready`) and async STT jobs (`status`) are eventually
   consistent — poll with backoff, don't assume immediate readiness.
6. **Respect Omni id types & envelopes:** contact/ticket ids are integers; list responses are
   `{ meta, payload }` and paginate with `?page=`.
7. **Don't double-charge:** for outbound calls and paid generations, verify state (search) before
   retrying a timed-out request.
8. **Read `detail` on errors** — especially `422` validation errors, which pinpoint the bad field.
9. **Full schema:** the complete OpenAPI spec is at `https://api.nabrah.ai/api/openapi.json`; the
   interactive "Try it" reference lives in the dashboard **Developers** tab.

### Machine-readable summary
```json
{
  "provider": "Nabrah AI",
  "product": "Saudi-native Arabic voice AI platform (TTS, STT, voice cloning, voice agents, SIP telephony, WhatsApp & omnichannel inbox)",
  "api_base_url": "https://api.nabrah.ai/api",
  "ext_base_url": "https://api.nabrah.ai/api/ext",
  "auth": { "type": "apiKey", "header": "X-API-Key", "prefix": "nb_", "obtain_at": "https://app.nabrah.ai (Developers tab)", "validate": "GET /api/ext/test" },
  "openapi": "https://api.nabrah.ai/api/openapi.json",
  "capabilities": ["text-to-speech", "streaming-tts", "voice-cloning", "speech-to-text", "async-transcription", "voice-agents", "outbound-calls", "web-calls", "sip-inbound", "sip-outbound", "function-tools", "call-analytics", "omnichannel-inbox", "whatsapp", "tickets", "contacts"],
  "languages": ["ar", "en"],
  "docs": "https://docs.nabrah.ai",
  "support": "support@nabrah.ai"
}
```

---

## 7. Links & support

- **Marketing site:** https://www.nabrah.ai
- **Developer docs:** https://docs.nabrah.ai
- **Dashboard (get API key, "Try it"):** https://app.nabrah.ai → Developers
- **OpenAPI spec:** https://api.nabrah.ai/api/openapi.json
- **AI discovery index:** https://www.nabrah.ai/llms.txt
- **Support:** support@nabrah.ai

*Nabrah (نبرة) is a Saudi-native voice AI platform for Arabic — hyper-realistic TTS, STT, voice
cloning, and autonomous voice agents tuned for Saudi and Gulf (Khaleeji) dialects, with APIs and
channels for phone, WhatsApp, and contact centers. This document is maintained for AI agents and
developers; it does not replace the pricing, terms, or privacy pages on the website.*
