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

# Voicemail

> Let callers leave a message when nobody answers, then list, play, and manage those messages.

Voicemail turns an **unanswered inbound call** into a message. Arm a Wave number,
and when a call to it goes no-answer, Wave plays a greeting, records the caller,
and files the recording as a voicemail you can list, play, mark heard, and delete.

<Note>
  Voicemail ships behind a per-environment flag. Until it is enabled for your
  account every endpoint returns `503 VOICEMAIL_DISABLED`.
</Note>

## Arm a number

`PUT /v1/numbers/{id}/voicemail` turns voicemail on or off for one of your Wave
numbers. Needs the `voicemail:write` scope.

```bash theme={null}
curl -X PUT https://api.wave.sa/v1/numbers/27d6dd39-…/voicemail \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "voicemail_enabled": true }'
```

```json theme={null}
{
  "id": "27d6dd39-…",
  "number": "+966590007001",
  "voicemail_enabled": true,
  "greeting_ref": null
}
```

### A custom greeting

By default Wave plays a standard greeting. To play your own, first upload a WAV
with [`POST /v1/callflows/audio`](/voice/ivr) — that returns an `audio_ref` — then
set it as `greeting_ref`:

```bash theme={null}
curl -X PUT https://api.wave.sa/v1/numbers/27d6dd39-…/voicemail \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "voicemail_enabled": true, "greeting_ref": "https://…/prompts/…wav" }'
```

`greeting_ref` must be a prompt **your organization** uploaded — an arbitrary URL
returns `400 VOICEMAIL_GREETING_INVALID`. Send `null` to clear it back to the
default; omit the key to leave the current greeting unchanged.

## List the box

`GET /v1/voicemails` returns your messages newest-first with cursor pagination.
Needs the `voicemail:read` scope.

```bash theme={null}
curl "https://api.wave.sa/v1/voicemails?limit=20" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"
```

```json theme={null}
{
  "data": [
    {
      "id": "9f2a…",
      "call_id": "27d6dd39-…",
      "number": "+966590007001",
      "from": "+966500000000",
      "duration_seconds": null,
      "recording_available": true,
      "transcription_status": "pending",
      "transcription_text": null,
      "listened_at": null,
      "created_at": "2026-09-07T10:00:00.000Z"
    }
  ],
  "next_cursor": "eyJ…",
  "limit": 20
}
```

Pass `next_cursor` back as `?cursor=` to page. Narrow the list with `?number=` (one
number's box) and `?listened=true|false` (heard vs new). Fetch one message with
`GET /v1/voicemails/{id}`.

<Note>
  Two fields are reserved for later additions: `transcription_status` is `pending`
  for now (automatic transcription comes later, and `transcription_text` stays
  `null` until then), and `duration_seconds` is `null` until the telephony engine
  reports a message length.
</Note>

## Play a message

`GET /v1/voicemails/{id}/recording-url` returns a **time-limited playback URL**
(valid \~60 minutes). Needs the `voicemail:read` scope and a **production** key.

```bash theme={null}
curl https://api.wave.sa/v1/voicemails/9f2a…/recording-url \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"
```

```json theme={null}
{
  "voicemail_id": "9f2a…",
  "url": "https://oss.example.com/…?signature=…",
  "expires_in_seconds": 3600
}
```

A message has audio only once the recording has been captured **and** storage is
provisioned; until then this returns `404 VOICEMAIL_RECORDING_NOT_FOUND` or
`503 VOICEMAIL_SERVICE_UNAVAILABLE`. Sandbox keys get `400 SANDBOX_KEY_NOT_ALLOWED`.

## Mark heard, and delete

`POST /v1/voicemails/{id}/listened` stamps the message as heard (the first time
only, so the "first heard" instant is stable). `DELETE /v1/voicemails/{id}`
soft-deletes it and clears its recording. Both need the `voicemail:write` scope.

```bash theme={null}
curl -X POST https://api.wave.sa/v1/voicemails/9f2a…/listened \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"

curl -X DELETE https://api.wave.sa/v1/voicemails/9f2a… \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx"
```

## Get notified

Instead of polling the box, subscribe to the `voicemail.received` [webhook](/webhooks)
— Wave posts it once a new message's recording is ready:

```json theme={null}
{
  "event": "voicemail.received",
  "data": {
    "voicemail_id": "9f2a…",
    "call_id": "27d6dd39-…",
    "number": "+966590007001",
    "from": "+966500000000",
    "duration_seconds": null,
    "recording_available": true,
    "transcription_status": "pending"
  }
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="WaveML" icon="code" href="/voice/waveml">
    The `<Play>` and `<Record>` verbs behind the voicemail flow.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/webhooks">
    Get `voicemail.received` pushed to you instead of polling.
  </Card>
</CardGroup>
