# Errors, retries and idempotency

> Read an error, decide whether to retry, and retry sends without ever sending twice.

Source: https://docs.vlayers.ai/developers/errors-and-retries

Kind: How-to guide

## What you'll build

Error handling that tells "fix the request" apart from "try again", and a retry loop for sends that is safe even when a request times out.

## Prerequisites

- A secret API key. See [Authentication and API keys](https://docs.vlayers.ai/get-started/authentication).
- Node.js 18 or later with `@voicelayer/api`, or Python 3.9 or later with `voicelayer`, or any HTTP client.

## Step 1: Read the error body

A refused request answers an HTTP status and a JSON body:

```json
{
  "error": "window_closed",
  "message": "The WhatsApp reply window for this conversation has closed.",
  "requestId": "req-8f2c1d4b",
  "retryable": false,
  "docs": "https://docs.vlayers.ai/errors/window_closed"
}
```

| Field | Use it to |
|---|---|
| `error` | Branch in code. It is a stable code; [every code](https://docs.vlayers.ai/errors) has a page. |
| `message` | Log it, or show it to a developer. It may change; don't match on it. |
| `requestId` | Quote it to support, or find the request in `GET /v1/request-logs`. |
| `retryable` | Decide whether the same request can succeed later. |
| `details` | Extra facts some codes carry, such as the missing scope. |
| `docs` | The code's page: what happened, why, and how to fix it. |

Authentication errors (`401 unauthorized`) and the event and webhook routes answer a smaller body, `{ "error", "message" }`.

In the TypeScript SDK, pass `throwOnError: true` and a refusal throws a `VoiceLayerApiError` with `status`, `code`, `requestId`, `retryable`, `details` and `docs`. In Python, a refusal raises a `voicelayer.ApiException` with `status` and `body`.

## Step 2: Decide whether to retry

- **4xx with `retryable: false`**: the request is wrong for now. Fix it; retrying the same request fails the same way.
- **`429`** (rate limit or test cap) and **`5xx`**: retry after a wait. Honor a `Retry-After` header when there is one.
- **A network error or timeout**: you don't know whether the request arrived. For a send, retry with the **same** idempotency key (Step 3).

Back off between retries: wait about 1 s, then 2 s, 4 s and so on, with some randomness, up to a cap.

## Step 3: Make sends idempotent

Send an `Idempotency-Key` header (1–255 printable characters; a UUID works) with every `POST /v1/messages` and `POST /v1/conversations/{id}/messages`. Make one key per message you mean to send, and reuse it only for retries of that message.

cURL:

```bash
curl -X POST https://api.vlayers.ai/v1/messages \
  -H "Authorization: Bearer $VOICELAYER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "channel": "sms",
    "to": "+15550123",
    "content": {
      "type": "text",
      "text": "Your table for two is booked for 7pm."
    }
  }'
```

TypeScript:

```ts
import { randomUUID } from 'node:crypto';
import { createVoiceLayerClient, sendMessage } from '@voicelayer/api';

const client = createVoiceLayerClient({ apiKey: process.env.VOICELAYER_API_KEY!, throwOnError: true });

const { data } = await sendMessage({
  client,
  headers: { 'idempotency-key': randomUUID() },
  body: {
    channel: 'sms',
    to: '+15550123',
    content: {
      type: 'text',
      text: 'Your table for two is booked for 7pm.'
    }
  }
});
console.log(data.message.id);
```

Python:

```python
import os
import uuid
import voicelayer

config = voicelayer.Configuration(access_token=os.environ["VOICELAYER_API_KEY"])
with voicelayer.ApiClient(config) as client:
    api = voicelayer.MessagesApi(client)
    result = api.send_message(
        send_message_request=voicelayer.SendMessageRequest.from_dict({
            "channel": "sms",
            "to": "+15550123",
            "content": {
                "type": "text",
                "text": "Your table for two is booked for 7pm.",
            },
        }),
        idempotency_key=str(uuid.uuid4()),
    )
    print(result.message.id)
```

What happens when you retry with the same key:

| Situation | Answer |
|---|---|
| The first request finished | The first result again, with the header `idempotency-replayed: true`. Nothing is sent twice. |
| The first request is still running | `409 send_in_progress`. Wait a few seconds and retry. |
| Same key, different body | `409 idempotency_conflict`. Use a new key for a new message. |

Without a key, a retried send can go out twice.

## Test it

With a test key, send the same request twice with the same `Idempotency-Key` value (set it yourself instead of `$(uuidgen)`). The second answer has `idempotency-replayed: true` and the same message id.

## Common errors

| Error | Status | How to fix |
|---|---|---|
| [`invalid_request`](https://docs.vlayers.ai/errors/invalid_request) | 400 | Read `message` and `details.issues` (the validation issues, by path), fix the request and send it again. |
| [`invalid_idempotency_key`](https://docs.vlayers.ai/errors/invalid_idempotency_key) | 400 | Send 1–255 printable ASCII characters (a UUID works), or leave the header out. |
| [`idempotency_conflict`](https://docs.vlayers.ai/errors/idempotency_conflict) | 409 | Use a new key for a new message; repeat the original body exactly to replay the original request. |
| [`send_in_progress`](https://docs.vlayers.ai/errors/send_in_progress) | 409 | Retry with the same key after `retry-after` (30 seconds). |
| [`rate_limited`](https://docs.vlayers.ai/errors/rate_limited) | 429 | Wait for `retry-after`, then retry (with the same Idempotency-Key for a send). |
| [`provider_retry_later`](https://docs.vlayers.ai/errors/provider_retry_later) | 503 | Retry after `retry-after`, with the same Idempotency-Key. |
| [`messaging_unavailable`](https://docs.vlayers.ai/errors/messaging_unavailable) | 503 | Retry shortly, with the same Idempotency-Key. |
| [`outcome_unknown`](https://docs.vlayers.ai/errors/outcome_unknown) | 409 | Do not resend under a new Idempotency-Key. Watch the message events (`message.sent`, `message.failed`) for the outcome. |

## Next steps

- [Every error code](https://docs.vlayers.ai/errors).
- [Rate limits](https://docs.vlayers.ai/developers/rate-limits).
- [Pagination](https://docs.vlayers.ai/developers/pagination).
