# WahaSend — documentation de l'API

> WahaSend envoie et reçoit des messages WhatsApp depuis votre propre numéro : envois, envois groupés, campagnes, rappels, codes OTP, médias, webhooks signés, boîte de réception, et un serveur MCP pour Claude et les autres agents IA. API HTTPS + JSON, base https://app.wahasend.com/api/v1, clé dans l'en-tête `X-API-Key`.

> Index de la documentation : https://wahasend.com/docs/fr/llms.txt · Tout en un fichier : https://wahasend.com/docs/fr.md · URL de base de l'API : https://app.wahasend.com/api/v1

## Premiers pas

WahaSend envoie et reçoit des messages WhatsApp depuis **votre propre numéro WhatsApp**. L'API est du simple HTTPS avec du JSON. Cinq minutes de zéro au premier message :

1. [Créez un compte](https://wahasend.com/register) et choisissez un produit. Il commence par un essai gratuit, et chaque produit vient avec une clé d'API ([les routes de chacun](https://wahasend.com/docs/fr/authentication.md#auth-products)).
2. Reliez votre numéro WhatsApp : tableau de bord → **WhatsApp**, scannez le QR code avec votre téléphone (ou utilisez le code d'association sur un téléphone).
3. Récupérez votre clé : tableau de bord → **Pour les développeurs** → **Générer une clé** (avec Connect : page **Connect**). Elle n'est affichée qu'une fois ; gardez-la comme un mot de passe.
4. Envoyez votre premier message :

```bash
curl https://app.wahasend.com/api/v1/alert/send \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone": "+22212345678", "message": "Hello from WahaSend"}'
```

Réponse (201):

```json
{
  "success": true,
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
  "status": "queued",
  "priority": "normal"
}
```

Le message part quelques secondes plus tard avec l'indicateur « écrit… ». Suivez-le avec `GET /messages` ou les [événements webhook](https://wahasend.com/docs/fr/webhooks.md).

<a id="base-url"></a>

#### URL de base et format

- Tous les chemins ci-dessous commencent par `https://app.wahasend.com/api/v1`.
- Envoyez du JSON avec `Content-Type: application/json` (10 Mo max par requête). Les réponses sont en JSON.
- Les réponses réussies portent `"success": true` (sauf pour l'instant les routes des [numéros WhatsApp](https://wahasend.com/docs/fr/api/numbers.md)). Les erreurs utilisent toujours [la même enveloppe](https://wahasend.com/docs/fr/errors.md).
- Les dates sont en ISO 8601 UTC (`2026-09-30T10:30:12.000Z`). Les identifiants sont des UUID.
- Chaque réponse a un en-tête `X-Request-Id` : donnez-le quand vous écrivez au support.

<a id="products"></a>

#### Ce que chaque produit débloque

Chaque endpoint ci-dessous indique la fonction nécessaire. Un compte peut avoir plusieurs produits et cumule leurs fonctions. Appeler une fonction non incluse répond 403 `PRODUCT_REQUIRED` avec `details.products`. Notify, Assistant et Shop permettent d'ajouter autant de numéros WhatsApp que nécessaire ; Developer et Connect en incluent 3, et les suivants sont des numéros supplémentaires (voir [Numéros WhatsApp](https://wahasend.com/docs/fr/api/numbers.md)).

| Produit | Fonctions | Clé d'API |
| --- | --- | --- |
| Notify | `send, batch, campaigns, reminders, api` | oui |
| Assistant | `send, bots, knowledge, api` | oui |
| Shop | `send, bots, commerce, api` | oui |
| Developer | `send, batch, api, otp, media, webhooks` | oui |
| Connect | `send, batch, inbox, mcp, api` | oui |

<a id="for-agents"></a>

#### Pour les agents IA

Cette documentation existe aussi en Markdown, pour les agents de code (Claude Code, Cursor, Codex…) et tout ce qui lit le web sans exécuter JavaScript : [llms.txt](https://wahasend.com/llms.txt) liste toutes les pages, [llms-full.txt](https://wahasend.com/llms-full.txt) les réunit en un fichier, et chaque page a sa propre adresse `.md` (par exemple [getting-started.md](https://wahasend.com/docs/getting-started.md) ; la version française est sous `/docs/fr/`). Pour qu'une application d'IA envoie et lise elle-même des messages WhatsApp, utilisez [Connect](https://wahasend.com/docs/fr/connect.md).

Les agents de code peuvent aussi installer le skill WahaSend (quoi appeler pour quoi, signatures des webhooks, erreurs, protéger le numéro), et les outils d'API importer [openapi.json](https://wahasend.com/openapi.json) (OpenAPI 3.1 : Postman, Insomnia, générateurs de SDK, actions GPT).

```bash
npx skills add ahmedEssyad/wahasendskills
```

Une consigne pour votre agent:

```text
Read https://wahasend.com/llms.txt and the pages it links to, then add WhatsApp notifications to this project with the WahaSend API. My key is in the WAHASEND_API_KEY environment variable.
```

---

## Authentification

Envoyez votre clé dans l'en-tête `X-API-Key` à chaque requête. Chaque produit inclut les clés d'API ; une clé atteint ce que vos produits incluent, rien de plus (403 `PRODUCT_REQUIRED` sinon).

```bash
curl https://app.wahasend.com/api/v1/me -H "X-API-Key: wn_8c1f…"
```

| Clé | Forme | Où | Ce qu'elle peut appeler |
| --- | --- | --- | --- |
| Clé du compte | `wn_…` | Tableau de bord → Pour les développeurs (ou la page Connect) | Tout ce que vos produits incluent, en tant qu'**admin** de l'organisation (pas l'équipe, la propriété ni la suppression). Choisissez un numéro avec `containerId`. |
| Clé de numéro | `waha_…` | WhatsApp → le numéro → Plus de réglages | Envoyer et lire **par ce numéro seulement** : `/alert/*`, `/otp/*`, `/recipients/*`, `/chats*`, `/contacts*`, `/bulk/*`, `/mcp`, et ses propres `/session/status\|qr\|pair\|start\|reconnect\|logout\|history\|profile`. Le reste répond 403 `NUMBER_KEY_SCOPE`. |

<a id="auth-products"></a>

#### Quels produits utilisent l'API

| Produit | Une clé atteint |
| --- | --- |
| **Notify** | `/alert/send`, `/alert/batch`, `/alert/validate`, `/recipients/validate`, [campagnes](https://wahasend.com/docs/fr/api/campaigns.md) `/bulk/*`, [rappels](https://wahasend.com/docs/fr/api/reminders.md) `/reminders/*` |
| **Assistant** · **Shop** | `/alert/send`, les routes d'assistant `/bots/*` (Shop : catalogue et commandes aussi), un [webhook](https://wahasend.com/docs/fr/webhooks.md) pour `bot.*` (et `commerce.order`) |
| **Developer** | Envois et lots, [OTP](https://wahasend.com/docs/fr/api/otp.md) `/otp/*`, envois de médias, modifier/supprimer/transférer, tous les événements [webhook](https://wahasend.com/docs/fr/webhooks.md) |
| **Connect** | Envois et lots, [conversations et contacts](https://wahasend.com/docs/fr/api/inbox.md), l'endpoint MCP `/mcp` |

Chaque produit atteint aussi les routes [compte et historique](https://wahasend.com/docs/fr/api/account.md) et [numéros WhatsApp](https://wahasend.com/docs/fr/api/numbers.md). OTP, envois de médias et l'ensemble des événements webhook restent dans Developer.

Donnez à chaque application la plus petite clé suffisante : une clé de numéro pour le paiement d'une boutique, la clé du compte pour votre back-office.

<a id="auth-bearer"></a>

#### Bearer, pour les applications d'IA

Beaucoup de clients MCP ne savent envoyer que `Authorization: Bearer …`. Sur l'endpoint MCP (`/mcp`), `Authorization: Bearer YOUR_KEY` marche exactement comme `X-API-Key`. Ailleurs, utilisez `X-API-Key` : `Bearer` y sert aux sessions du tableau de bord.

<a id="auth-rotation"></a>

#### Remplacer une clé

Régénérez une clé depuis la même page (ou `POST /me/regenerate-key`, `POST /session/:containerId/regenerate-key`). L'ancienne cesse de fonctionner **aussitôt** : préparez vos serveurs, régénérez, puis déployez la nouvelle clé. Il n'y a qu'une clé de compte à la fois. Les clés sont stockées hachées : une clé perdue ne peut pas être réaffichée, seulement remplacée.

> Une clé envoie des messages depuis votre vrai numéro. Gardez-la sur votre serveur, jamais dans une page web ou une application mobile.

Un compte désactivé répond 403 `ACCOUNT_DEACTIVATED` ; une clé absente ou fausse 401 `AUTH_ERROR` (`details.reason` : `missing_credentials` ou `invalid_api_key`).

---

## Numéros et @lid

Écrivez les numéros au format international : indicatif puis numéro, avec ou sans `+`. Espaces, tirets et parenthèses sont retirés. `+222 12 34 56 78`, `22212345678` et `22212345678@c.us` sont la même personne. Pas de `0` initial ni de `00`.

<a id="several-numbers"></a>

#### Plusieurs numéros

WhatsApp peut bloquer un numéro. Pour continuer, une organisation peut envoyer par plusieurs de ses numéros (page Numéros WhatsApp → Plus de réglages → Plusieurs numéros, ou `PUT /session/send-group`). C'est désactivé par défaut : tout marche alors comme avant, depuis votre numéro par défaut.

- **S'applique** aux envois qui ne nomment pas de numéro : `/alert/send`, `/alert/batch`, `/alert/send-*`, campagnes et rappels, depuis le tableau de bord ou la clé du compte. Un `containerId` explicite ou une clé de numéro l'emporte toujours et ne bouge jamais. Connect (Claude) et les assistants restent sur leur propre numéro.
- **backup** : votre numéro par défaut, puis le suivant du groupe quand il est déconnecté ou peut-être bloqué. **share** : les messages sont répartis sur les numéros connectés du groupe.
- **Même contact, même numéro** (activé par défaut) : un contact reçoit toujours les messages du même numéro tant qu'il marche. S'il tombe, le contact passe à un autre numéro du groupe et y reste.
- Un message en attente dont le numéro tombe avant l'envoi passe à un numéro connecté du groupe (jamais en dehors). Ce qui a peut-être déjà été envoyé ne repart jamais.
- Les réponses portent `containerId` : le numéro du message. `GET /messages` et les [webhooks](https://wahasend.com/docs/fr/webhooks.md) `message.*` montrent le numéro qui l'a vraiment envoyé.
- Les limites restent par numéro (et par organisation) : plusieurs numéros répartissent la charge, ils ne relèvent aucune limite. Utiliser plusieurs numéros pour contourner un blocage peut les faire bloquer aussi : c'est pour la continuité, pas pour l'envoi en masse.

<a id="lid"></a>

#### Identifiants finissant par @lid

WhatsApp cache désormais le numéro de beaucoup de gens derrière un **Linked ID** comme `104937261508834@lid`. C'est ainsi que leurs messages vous arrivent, et c'est une adresse valide : écrivez-y exactement tel que reçu. N'enlevez jamais `@lid` et ne traitez pas ses chiffres comme un numéro.

- Accepté partout où un champ dit « ou un identifiant WhatsApp » : envois de texte et de médias, lots, vérification (toujours compté valide : il n'y a pas de numéro à vérifier).
- Pas accepté par l'OTP, les campagnes, les rappels et le `toPhone` du transfert : il leur faut un vrai numéro.
- Dans les conversations, une personne est identifiée par son numéro quand WhatsApp nous l'a donné, sinon par l'identifiant `@lid`.

---

## Envoyer des messages

Un message texte est inclus dans chaque produit. Photos, fichiers, notes vocales, contacts, positions, liens, sondages, transfert, modification et suppression viennent avec **Developer** (fonction `media`).

<a id="alert-send"></a>

#### Envoyer un message texte

`POST /api/v1/alert/send`

Met un message texte en file d'attente. Il part quelques secondes plus tard avec l'indicateur « écrit… », comme un humain (`high` saute cette étape). Le numéro est d'abord vérifié sur WhatsApp.

- Débloqué par: tous les produits (`send`)
- Clé de numéro : oui

> Suivez le message avec `GET /messages` ou les [événements webhook](https://wahasend.com/docs/fr/webhooks.md) `message.*`. `messageId` est l'identifiant WahaSend, pas celui de WhatsApp. `containerId` est le numéro qui l'envoie ; avec [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers), il peut passer à un autre numéro du groupe si celui-ci tombe, et `GET /messages` et les webhooks montrent alors le numéro qui l'a vraiment envoyé.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `message` | string | oui | Le texte, 1 à 4096 caractères. Les liens ont un aperçu. |
| `priority` | "high" \| "normal" \| "low" | non | Par défaut `normal`. `high` passe devant et saute la simulation de frappe ; à garder pour les alertes urgentes. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "message": "Your order #1042 is ready for pickup.",
  "priority": "normal"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "message": "Your order #1042 is ready for pickup.",
    "priority": "normal"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "message": "Your order #1042 is ready for pickup.",
        "priority": "normal",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
  "status": "queued",
  "priority": "normal",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NUMBER_NOT_ON_WHATSAPP`](https://wahasend.com/docs/fr/errors.md#number_not_on_whatsapp) (400): This number is not on WhatsApp.
- [`NUMBER_VALIDATION_UNAVAILABLE`](https://wahasend.com/docs/fr/errors.md#number_validation_unavailable) (503): Numbers cannot be checked right now. Try again shortly.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="alert-send-image"></a>

#### Envoyer une photo

`POST /api/v1/alert/send-image`

Envoie une image depuis un lien, avec une légende facultative.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `imageUrl` | string (URL) | oui | Lien vers l'image (JPEG, PNG ou WebP). Doit être une adresse publique : les adresses privées ou locales sont refusées. |
| `caption` | string | non | Texte sous le fichier, 1024 caractères max. |
| `filename` | string | non | Nom du fichier affiché au contact, 255 caractères max. |
| `mimetype` | string | non | Type MIME, p. ex. `application/pdf` (100 caractères max). Deviné s'il manque. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-image \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "imageUrl": "https://example.com/menu.jpg",
  "caption": "Today'\''s menu"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-image', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "imageUrl": "https://example.com/menu.jpg",
    "caption": "Today's menu"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-image",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "imageUrl": "https://example.com/menu.jpg",
        "caption": "Today's menu",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-video"></a>

#### Envoyer une vidéo

`POST /api/v1/alert/send-video`

Envoie une vidéo MP4 depuis un lien.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `videoUrl` | string (URL) | oui | Lien vers le fichier MP4. Doit être une adresse publique : les adresses privées ou locales sont refusées. |
| `caption` | string | non | Texte sous le fichier, 1024 caractères max. |
| `filename` | string | non | Nom du fichier affiché au contact, 255 caractères max. |
| `mimetype` | string | non | Type MIME, p. ex. `application/pdf` (100 caractères max). Deviné s'il manque. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-video \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "videoUrl": "https://example.com/demo.mp4",
  "caption": "How it works"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-video', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "videoUrl": "https://example.com/demo.mp4",
    "caption": "How it works"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-video",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "videoUrl": "https://example.com/demo.mp4",
        "caption": "How it works",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-file"></a>

#### Envoyer un fichier

`POST /api/v1/alert/send-file`

Envoie un document (PDF, tableur…) depuis un lien.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `fileUrl` | string (URL) | oui | Lien vers le fichier. Doit être une adresse publique : les adresses privées ou locales sont refusées. |
| `filename` | string | non | Nom du fichier affiché au contact, 255 caractères max. |
| `caption` | string | non | Texte sous le fichier, 1024 caractères max. |
| `mimetype` | string | non | Type MIME, p. ex. `application/pdf` (100 caractères max). Deviné s'il manque. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-file \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "fileUrl": "https://example.com/invoice-1042.pdf",
  "filename": "invoice-1042.pdf",
  "caption": "Your invoice"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-file', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "fileUrl": "https://example.com/invoice-1042.pdf",
    "filename": "invoice-1042.pdf",
    "caption": "Your invoice"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-file",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "fileUrl": "https://example.com/invoice-1042.pdf",
        "filename": "invoice-1042.pdf",
        "caption": "Your invoice",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-voice"></a>

#### Envoyer une note vocale

`POST /api/v1/alert/send-voice`

Envoie un fichier audio comme note vocale. Utilisez OGG/Opus pour qu'il s'affiche en note vocale.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `audioUrl` | string (URL) | oui | Lien vers le fichier audio. Doit être une adresse publique : les adresses privées ou locales sont refusées. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-voice \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "audioUrl": "https://example.com/welcome.ogg"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-voice', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "audioUrl": "https://example.com/welcome.ogg"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-voice",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "audioUrl": "https://example.com/welcome.ogg",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-contact"></a>

#### Partager des contacts

`POST /api/v1/alert/send-contact`

Envoie 1 à 10 fiches contact.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `contacts` | array | oui | 1 à 10 contacts. |
| `contacts[].fullName` | string | oui | Nom, 1 à 255 caractères. |
| `contacts[].phoneNumber` | string | oui | Son numéro, 5 à 20 caractères. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-contact \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "contacts": [
    {
      "fullName": "Front desk",
      "phoneNumber": "+22245250000"
    }
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-contact', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "contacts": [
      {
        "fullName": "Front desk",
        "phoneNumber": "+22245250000"
      }
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-contact",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "contacts": [
            {
                "fullName": "Front desk",
                "phoneNumber": "+22245250000",
            },
        ],
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-location"></a>

#### Envoyer une position

`POST /api/v1/alert/send-location`

Envoie un point sur la carte.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `latitude` | number | oui | De −90 à 90. |
| `longitude` | number | oui | De −180 à 180. |
| `title` | string | non | Nom du lieu, 255 caractères max. |
| `address` | string | non | Adresse, 500 caractères max. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-location \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "latitude": 18.0858,
  "longitude": -15.9785,
  "title": "Our shop",
  "address": "Tevragh Zeina, Nouakchott"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-location', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "latitude": 18.0858,
    "longitude": -15.9785,
    "title": "Our shop",
    "address": "Tevragh Zeina, Nouakchott"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-location",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "latitude": 18.0858,
        "longitude": -15.9785,
        "title": "Our shop",
        "address": "Tevragh Zeina, Nouakchott",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-link"></a>

#### Envoyer un lien avec un aperçu choisi

`POST /api/v1/alert/send-link`

Envoie un texte avec un aperçu de lien que vous choisissez (titre, description, image).

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `text` | string | oui | Texte du message, 1 à 4096 caractères. Mettez-y le lien. |
| `previewUrl` | string (URL) | oui | Le lien concerné par l'aperçu. Doit être une adresse publique : les adresses privées ou locales sont refusées. |
| `previewTitle` | string | oui | Titre de l'aperçu, 1 à 255 caractères. |
| `previewDescription` | string | non | 500 caractères max. |
| `previewImageUrl` | string (URL) | non | Lien public vers l'image de l'aperçu. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-link \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "text": "Track your order: https://example.com/t/1042",
  "previewUrl": "https://example.com/t/1042",
  "previewTitle": "Order #1042"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-link', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "text": "Track your order: https://example.com/t/1042",
    "previewUrl": "https://example.com/t/1042",
    "previewTitle": "Order #1042"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-link",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "text": "Track your order: https://example.com/t/1042",
        "previewUrl": "https://example.com/t/1042",
        "previewTitle": "Order #1042",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-send-poll"></a>

#### Envoyer un sondage

`POST /api/v1/alert/send-poll`

Envoie un sondage de 2 à 12 choix. Les votes arrivent en [événements webhook](https://wahasend.com/docs/fr/webhooks.md) `poll.vote`.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Envoyé tout de suite par votre numéro (pas de file d'attente, pas de simulation de frappe) et enregistré dans `GET /messages` comme `sent` : les [événements](https://wahasend.com/docs/fr/webhooks.md) `message.delivered` / `message.read` suivent. `messageId` est l'identifiant WahaSend (pour modifier, supprimer ou transférer le message) ; `wahaMessageId` est celui de WhatsApp.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international (`+22212345678`) ou identifiant WhatsApp finissant par `@lid`. Voir [Numéros](https://wahasend.com/docs/fr/phone-numbers.md). |
| `question` | string | oui | 1 à 255 caractères. |
| `options` | string[] | oui | 2 à 12 choix de 1 à 100 caractères. |
| `multipleAnswers` | boolean | non | Autoriser plusieurs réponses. Par défaut `false`. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/send-poll \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "question": "Which delivery day suits you?",
  "options": [
    "Monday",
    "Wednesday",
    "Friday"
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/send-poll', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "question": "Which delivery day suits you?",
    "options": [
      "Monday",
      "Wednesday",
      "Friday"
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/send-poll",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "question": "Which delivery day suits you?",
        "options": [
            "Monday",
            "Wednesday",
            "Friday",
        ],
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-forward"></a>

#### Transférer un message

`POST /api/v1/alert/forward`

Transfère un message de votre numéro vers une autre conversation. Le destinataire voit « Transféré ». Enregistré dans `GET /messages` comme les envois de médias.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `fromPhone` | string | non | Inutile (WhatsApp retrouve le message par son identifiant) ; toujours accepté. |
| `messageId` | string | oui | Le message : le `messageId` WahaSend d'un message envoyé, ou un identifiant WhatsApp (p. ex. le `wahaMessageId` d'un événement `message.inbound`). |
| `toPhone` | string | oui | Numéro international, 8 à 20 caractères (`+22212345678`). Les identifiants `@lid` ne sont pas acceptés ici. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/forward \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "messageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "toPhone": "+22287654321"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/forward', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "messageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
    "toPhone": "+22287654321"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/forward",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "messageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
        "toPhone": "+22287654321",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "messageId": "3d7f1b2c-5e8a-4c69-b0d3-8f2a6c4e1b95",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
}
```

**Erreurs possibles**

- [`MESSAGE_NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#message_not_found) (404): No message with this id was sent by this account.
- [`MESSAGE_NOT_SENT`](https://wahasend.com/docs/fr/errors.md#message_not_sent) (409): This message has not been sent yet (or it failed). Only sent messages can be edited or deleted.
- [`MESSAGE_WRONG_NUMBER`](https://wahasend.com/docs/fr/errors.md#message_wrong_number) (403): This message was sent from another WhatsApp number than the one this request uses.
- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-edit"></a>

#### Modifier un message envoyé

`PUT /api/v1/alert/edit`

Change le texte d'un message envoyé par votre numéro. WhatsApp ne le permet que peu de temps après l'envoi.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Avec l'identifiant WahaSend le message est retrouvé dans votre historique : il doit être parti (`sent`, `delivered` ou `read`, sinon `MESSAGE_NOT_SENT`), à vous (sinon `MESSAGE_NOT_FOUND`) et envoyé par le numéro de la requête (clé de numéro ou `containerId` ; sinon `MESSAGE_WRONG_NUMBER`). Son texte dans `GET /messages` est aussi mis à jour.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `messageId` | string | oui | Le message à modifier. Le `messageId` WahaSend (de `/alert/send`, d'un envoi de média ou de `GET /messages` ; le message doit être parti), ou l'identifiant WhatsApp comme avant (`phone` est alors obligatoire). |
| `text` | string | oui | Nouveau texte, 1 à 4096 caractères. |
| `phone` | string | non | Le numéro ou l'identifiant `@lid` de la conversation. Obligatoire avec un identifiant WhatsApp ; avec l'identifiant WahaSend il vient du message (s'il est donné, il doit correspondre). |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/alert/edit \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
  "text": "Your order #1042 is ready (counter 2)."
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/edit', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
    "text": "Your order #1042 is ready (counter 2)."
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/alert/edit",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
        "text": "Your order #1042 is ready (counter 2).",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F"
}
```

**Erreurs possibles**

- [`MESSAGE_NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#message_not_found) (404): No message with this id was sent by this account.
- [`MESSAGE_NOT_SENT`](https://wahasend.com/docs/fr/errors.md#message_not_sent) (409): This message has not been sent yet (or it failed). Only sent messages can be edited or deleted.
- [`MESSAGE_WRONG_NUMBER`](https://wahasend.com/docs/fr/errors.md#message_wrong_number) (403): This message was sent from another WhatsApp number than the one this request uses.
- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="alert-delete"></a>

#### Supprimer un message envoyé

`POST /api/v1/alert/delete`

Supprime pour tous un message envoyé par votre numéro.

- Débloqué par: Developer (`media`)
- Clé de numéro : oui

> Mêmes règles que [modifier](https://wahasend.com/docs/fr/api/sending.md#alert-edit). Avec un identifiant WhatsApp, envoyez aussi `phone` : `{ "phone": "+22212345678", "messageId": "true_…" }`.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `messageId` | string | oui | Le message à supprimer. Le `messageId` WahaSend (de `/alert/send`, d'un envoi de média ou de `GET /messages` ; le message doit être parti), ou l'identifiant WhatsApp comme avant (`phone` est alors obligatoire). |
| `phone` | string | non | Le numéro ou l'identifiant `@lid` de la conversation. Obligatoire avec un identifiant WhatsApp ; avec l'identifiant WahaSend il vient du message (s'il est donné, il doit correspondre). |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/delete \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/delete', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/delete",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
  "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F"
}
```

**Erreurs possibles**

- [`MESSAGE_NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#message_not_found) (404): No message with this id was sent by this account.
- [`MESSAGE_NOT_SENT`](https://wahasend.com/docs/fr/errors.md#message_not_sent) (409): This message has not been sent yet (or it failed). Only sent messages can be edited or deleted.
- [`MESSAGE_WRONG_NUMBER`](https://wahasend.com/docs/fr/errors.md#message_wrong_number) (403): This message was sent from another WhatsApp number than the one this request uses.
- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

---

## Vérifier des numéros

Sachez quels numéros sont sur WhatsApp avant d'écrire. Inclus dans **Notify**, **Developer** et **Connect** (fonction `batch`). Rien n'est envoyé.

<a id="alert-validate"></a>

#### Vérifier jusqu'à 500 numéros

`POST /api/v1/alert/validate`

Renvoie les numéros présents sur WhatsApp et les autres, en chiffres. Les identifiants `@lid` comptent comme valides.

- Débloqué par: Notify, Developer, Connect (`batch`)
- Clé de numéro : oui

> Quand **aucun** numéro n'est sur WhatsApp, la réponse est une erreur 400 `NO_VALID_NUMBERS` avec `details.invalidNumbers`, pas une liste `valid` vide.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phones` | string[] | oui | 1 à 500 numéros ou identifiants `@lid`. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/validate \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phones": [
    "+22212345678",
    "+22287654321"
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/validate', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phones": [
      "+22212345678",
      "+22287654321"
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/validate",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phones": [
            "+22212345678",
            "+22287654321",
        ],
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "valid": [
    "22212345678"
  ],
  "invalid": [
    "22287654321"
  ],
  "total": 2
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_VALID_NUMBERS`](https://wahasend.com/docs/fr/errors.md#no_valid_numbers) (400): None of these numbers are on WhatsApp.
- [`NUMBER_VALIDATION_UNAVAILABLE`](https://wahasend.com/docs/fr/errors.md#number_validation_unavailable) (503): Numbers cannot be checked right now. Try again shortly.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

<a id="recipients-validate"></a>

#### Vérifier une longue liste (jusqu'à 10 000)

`POST /api/v1/recipients/validate`

Pour les listes de contacts : enlève les doublons, vérifie le format, puis interroge WhatsApp cinq numéros à la fois. Plus lent ; à utiliser avant une campagne.

- Débloqué par: Notify, Developer, Connect (`batch`)
- Clé de numéro : oui

> Un numéro que WhatsApp n'a pas pu vérifier est listé valide avec un `chatId` vide.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phones` | string[] | oui | 1 à 10 000 numéros. Espaces, tirets et parenthèses sont retirés. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/recipients/validate \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phones": [
    "+222 12 34 56 78",
    "+22287654321",
    "12"
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/recipients/validate', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phones": [
      "+222 12 34 56 78",
      "+22287654321",
      "12"
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/recipients/validate",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phones": [
            "+222 12 34 56 78",
            "+22287654321",
            "12",
        ],
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "total": 3,
  "valid": 1,
  "invalid": 2,
  "validNumbers": [
    {
      "phone": "+22212345678",
      "chatId": "22212345678@c.us"
    }
  ],
  "invalidNumbers": [
    {
      "phone": "12",
      "reason": "Invalid format",
      "code": "invalid_format"
    },
    {
      "phone": "+22287654321",
      "reason": "Not on WhatsApp",
      "code": "not_on_whatsapp"
    }
  ]
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

---

## Envois groupés

Le même texte à 500 personnes max en un appel. Inclus dans **Notify**, **Developer** et **Connect** (fonction `batch`). Pour des textes personnalisés et programmés, utilisez les [campagnes](https://wahasend.com/docs/fr/api/campaigns.md).

<a id="alert-batch"></a>

#### Envoyer un texte à plusieurs

`POST /api/v1/alert/batch`

Vérifie chaque numéro sur WhatsApp, saute ceux qui n'y sont pas et met en file un message par destinataire restant.

- Débloqué par: Notify, Developer, Connect (`batch`)
- Clé de numéro : oui

> Envoyer le même texte à beaucoup de gens qui ne l'ont pas demandé est le moyen le plus rapide de faire bloquer un numéro. Voir [Bonnes pratiques](https://wahasend.com/docs/fr/good-practice.md).

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `recipients` | string[] | oui | 1 à 500 numéros ou identifiants `@lid`. |
| `message` | string | oui | 1 à 4096 caractères. |
| `priority` | "high" \| "normal" \| "low" | non | Par défaut `normal`. |
| `skipInvalidNumbers` | boolean | non | Par défaut `true` : les numéros absents de WhatsApp sont sautés et listés dans `invalidNumbers`. `false` : si un numéro n'est pas sur WhatsApp, rien n'est envoyé (`NUMBER_NOT_ON_WHATSAPP` avec `details.invalidNumbers`). |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/alert/batch \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "recipients": [
    "+22212345678",
    "+22287654321"
  ],
  "message": "We are closed on Friday for the holiday."
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/alert/batch', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "recipients": [
      "+22212345678",
      "+22287654321"
    ],
    "message": "We are closed on Friday for the holiday."
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/alert/batch",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "recipients": [
            "+22212345678",
            "+22287654321",
        ],
        "message": "We are closed on Friday for the holiday.",
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "count": 1,
  "messages": [
    {
      "phone": "22212345678",
      "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
      "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
    }
  ],
  "invalidNumbers": [
    "22287654321"
  ],
  "skipped": 1
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NO_VALID_NUMBERS`](https://wahasend.com/docs/fr/errors.md#no_valid_numbers) (400): None of these numbers are on WhatsApp.
- [`NUMBER_NOT_ON_WHATSAPP`](https://wahasend.com/docs/fr/errors.md#number_not_on_whatsapp) (400): This number is not on WhatsApp.
- [`NUMBER_VALIDATION_UNAVAILABLE`](https://wahasend.com/docs/fr/errors.md#number_validation_unavailable) (503): Numbers cannot be checked right now. Try again shortly.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

---

## Campagnes

Une campagne envoie un modèle avec des `{{variables}}` à une liste, avec 3 à 7 secondes entre deux messages, tout de suite ou à une heure donnée. Inclus dans **Notify** (fonction `campaigns`). Statuts : `draft` ⇄ `scheduled` → `sending` ⇄ `paused` → `completed` ; `scheduled` ou `paused` → `cancelled`. `stats` compte `sent`, `failed`, `delivered` et `read` (d'après les accusés WhatsApp, chaque message une fois).

<a id="campaign-create"></a>

#### Créer une campagne

`POST /api/v1/bulk/campaigns`

L'enregistre en `draft`, ou `scheduled` si vous donnez `scheduledAt`. Rien ne part avant l'appel à send (ou l'heure prévue).

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `name` | string | oui | 1 à 255 caractères. |
| `template` | string | oui | 1 à 4096 caractères. Les champs comme `{{name}}` sont remplis avec les `variables` de chaque destinataire (clés dans n'importe quelle langue). |
| `recipients` | array | oui | 1 à 10 000 destinataires. |
| `recipients[].phone` | string | oui | Numéro international, 8 à 20 caractères (`+22212345678`). Les identifiants `@lid` ne sont pas acceptés ici. |
| `recipients[].variables` | object (string → string) | non | Valeurs du modèle. |
| `scheduledAt` | string (ISO 8601) | non | Quand commencer, avec n'importe quel décalage : `2026-10-05T08:00:00Z` ou `2026-10-05T08:00:00+00:00` (Nouakchott est en UTC). Vérifié chaque minute ; part de votre numéro par défaut. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/bulk/campaigns \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Ramadan hours",
  "template": "Hello {{name}}, from Monday we open 10:00–16:00.",
  "recipients": [
    {
      "phone": "+22212345678",
      "variables": {
        "name": "Aïcha"
      }
    },
    {
      "phone": "+22287654321",
      "variables": {
        "name": "Moussa"
      }
    }
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "name": "Ramadan hours",
    "template": "Hello {{name}}, from Monday we open 10:00–16:00.",
    "recipients": [
      {
        "phone": "+22212345678",
        "variables": {
          "name": "Aïcha"
        }
      },
      {
        "phone": "+22287654321",
        "variables": {
          "name": "Moussa"
        }
      }
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/bulk/campaigns",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "name": "Ramadan hours",
        "template": "Hello {{name}}, from Monday we open 10:00–16:00.",
        "recipients": [
            {
                "phone": "+22212345678",
                "variables": {
                    "name": "Aïcha",
                },
            },
            {
                "phone": "+22287654321",
                "variables": {
                    "name": "Moussa",
                },
            },
        ],
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "campaign": {
    "id": "c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
    "name": "Ramadan hours",
    "status": "draft",
    "recipientCount": 2,
    "scheduledAt": null
  }
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.

<a id="campaign-list"></a>

#### Lister les campagnes

`GET /api/v1/bulk/campaigns`

Toutes les campagnes de l'organisation, les plus anciennes d'abord.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/bulk/campaigns \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/bulk/campaigns",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "campaigns": [
    {
      "id": "c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
      "name": "Ramadan hours",
      "template": "Hello {{name}}, from Monday we open 10:00–16:00.",
      "status": "completed",
      "stats": {
        "total": 2,
        "sent": 2,
        "delivered": 2,
        "failed": 0,
        "read": 1
      },
      "scheduledAt": null,
      "createdAt": "2026-09-30T09:12:44.120Z"
    }
  ]
}
```

<a id="campaign-status"></a>

#### Statut d'une campagne

`GET /api/v1/bulk/campaigns/:id/status`

Où en est une campagne, avec ses destinataires et ses compteurs.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

> `delivered` compte un message la première fois que WhatsApp le dit remis ou lu ; `read` la première fois qu'il est lu. Les campagnes d'avant le 2026-10-01 n'ont pas de `read`.

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/status \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/status', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/status",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "id": "c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
  "name": "Ramadan hours",
  "template": "Hello {{name}}, from Monday we open 10:00–16:00.",
  "recipients": [
    {
      "phone": "+22212345678",
      "variables": {
        "name": "Aïcha"
      }
    }
  ],
  "status": "sending",
  "stats": {
    "total": 2,
    "sent": 1,
    "delivered": 1,
    "failed": 0,
    "read": 0
  },
  "scheduledAt": null,
  "startedAt": "2026-09-30T09:15:02.000Z",
  "completedAt": null,
  "createdAt": "2026-09-30T09:12:44.120Z"
}
```

**Erreurs possibles**

- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="campaign-update"></a>

#### Modifier avant l'envoi

`PUT /api/v1/bulk/campaigns/:id`

Change le nom, le modèle, les destinataires ou l'heure de départ d'une campagne `draft` ou `scheduled`.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `name` | string | non | 1 à 255 caractères. |
| `template` | string | non | 1 à 4096 caractères. |
| `recipients` | array | non | Remplace toute la liste (1 à 10 000). |
| `recipients[].phone` | string | oui | Numéro international, 8 à 20 caractères (`+22212345678`). Les identifiants `@lid` ne sont pas acceptés ici. |
| `recipients[].variables` | object (string → string) | non | Valeurs du modèle. |
| `scheduledAt` | string (ISO 8601) \| null | non | Une nouvelle heure (tout décalage) la rend `scheduled` ; `null` la remet en `draft`. |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71 \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "template": "Hello {{name}}, from Monday we open 10:00–16:30.",
  "scheduledAt": "2026-10-05T08:00:00+00:00"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "template": "Hello {{name}}, from Monday we open 10:00–16:30.",
    "scheduledAt": "2026-10-05T08:00:00+00:00"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "template": "Hello {{name}}, from Monday we open 10:00–16:30.",
        "scheduledAt": "2026-10-05T08:00:00+00:00",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "campaign": {
    "id": "c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
    "name": "Ramadan hours",
    "template": "Hello {{name}}, from Monday we open 10:00–16:30.",
    "status": "scheduled",
    "stats": {
      "total": 2,
      "sent": 0,
      "delivered": 0,
      "failed": 0
    },
    "scheduledAt": "2026-10-05T08:00:00.000Z",
    "createdAt": "2026-09-30T09:12:44.120Z"
  }
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.

<a id="campaign-send"></a>

#### Lancer l'envoi

`POST /api/v1/bulk/campaigns/:id/send`

Lance maintenant une campagne `draft` ou `scheduled`. Les messages partent à 3–7 secondes d'intervalle.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/send \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/send', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/send",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "campaignId": "c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
  "status": "sending",
  "totalRecipients": 2,
  "estimatedDuration": "1 minutes"
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

<a id="campaign-pause"></a>

#### Mettre en pause

`POST /api/v1/bulk/campaigns/:id/pause`

Arrête les messages pas encore partis. Seule une campagne `sending` peut être mise en pause.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/pause \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/pause', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/pause",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "status": "paused",
  "jobsRemoved": 118
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="campaign-resume"></a>

#### Reprendre

`POST /api/v1/bulk/campaigns/:id/resume`

Envoie ce qu'il restait à une campagne `paused`, jamais depuis le début.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/resume \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/resume', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/resume",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "status": "sending",
  "jobsRequeued": 118
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

<a id="campaign-delete"></a>

#### Supprimer

`DELETE /api/v1/bulk/campaigns/:id`

Supprime une campagne qui n'est pas `sending` (mettez-la en pause d'abord) et son historique de messages.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Exemple**

curl:

```bash
curl -X DELETE https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71 \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71', {
  method: 'DELETE',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.delete(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="campaign-cancel"></a>

#### Annuler

`POST /api/v1/bulk/campaigns/:id/cancel`

Annule pour de bon une campagne `scheduled` ou `paused` : statut `cancelled`, les messages pas encore partis sont abandonnés (marqués `failed`, erreur « Campaign cancelled »). Pour arrêter une campagne `sending`, mettez-la d'abord en pause.

- Débloqué par: Notify (`campaigns`) + Developer ou Connect pour une clé d'API
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant de la campagne. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/cancel \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/cancel', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/bulk/campaigns/c1f9a2d4-6b3e-4a7c-9d15-3e8b2f6a0c71/cancel",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "status": "cancelled",
  "jobsRemoved": 0,
  "messagesCancelled": 118
}
```

**Erreurs possibles**

- [`CAMPAIGN_INVALID_STATE`](https://wahasend.com/docs/fr/errors.md#campaign_invalid_state) (400): The campaign cannot do this in its current state.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

---

## Rappels

Un rappel est un message à une personne à une heure donnée, une fois ou chaque jour, semaine ou mois (le mensuel garde le jour du mois ; le 31 devient le dernier jour des mois plus courts). Vérifié toutes les 30 secondes. Inclus dans **Notify** (fonction `reminders`). Statuts : `scheduled`, `processing`, `done`, `failed`, `cancelled`.

<a id="reminder-list"></a>

#### Lister les rappels

`GET /api/v1/reminders`

Les prochains d'abord (le plus proche en tête), puis les autres (dernier changement en tête).

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `status` | string | non | `scheduled`, `processing`, `done`, `failed` ou `cancelled`. |
| `search` | string | non | Partie du numéro ou du libellé (100 caractères max). |
| `page` | number | non | Par défaut 1. |
| `limit` | number | non | Par défaut 50, max 100. |

**Exemple**

curl:

```bash
curl "https://app.wahasend.com/api/v1/reminders?status=scheduled&limit=20" \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders?status=scheduled&limit=20', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/reminders?status=scheduled&limit=20",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "total": 1,
  "page": 1,
  "limit": 20,
  "reminders": [
    {
      "id": "a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
      "ownerId": "2c7d0e19-4b8a-4f63-9e21-7a5c3d9b1f08",
      "containerId": null,
      "label": "Rent — flat 3",
      "phone": "+22212345678",
      "message": "Hello {{name}}, your rent of {{amount}} is due tomorrow.",
      "variables": {
        "name": "Aïcha",
        "amount": "45 000 MRU"
      },
      "sendAt": "2026-11-01T09:00:00.000Z",
      "firstSendAt": "2026-10-01T09:00:00.000Z",
      "repeat": "monthly",
      "status": "scheduled",
      "batchId": null,
      "sendCount": 1,
      "lastSentAt": "2026-10-01T09:00:03.412Z",
      "lastMessageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
      "lastError": null,
      "createdAt": "2026-09-28T14:02:11.000Z",
      "updatedAt": "2026-10-01T09:00:03.500Z",
      "lastMessageStatus": "read"
    }
  ]
}
```

<a id="reminder-create"></a>

#### Créer un rappel

`POST /api/v1/reminders`

Programme un message, éventuellement répété. Les `{{variables}}` du texte sont remplies à l'envoi.

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international ; espaces, tirets, points et parenthèses sont retirés. |
| `message` | string | oui | 1 à 4096 caractères, avec des `{{variables}}` possibles. |
| `sendAt` | string (ISO 8601) | oui | Premier envoi, avec `Z` ou un décalage (`2026-10-01T09:00:00+00:00`). 5 minutes dans le passé au plus. |
| `repeat` | "none" \| "daily" \| "weekly" \| "monthly" | non | Par défaut `none`. |
| `label` | string \| null | non | Votre note, 255 caractères max. |
| `variables` | object (string → string) | non | 50 valeurs max (clés ≤ 100, valeurs ≤ 1000 caractères). |
| `containerId` | uuid \| null | non | Numéro d'envoi ; `null` = votre numéro par défaut. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/reminders \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "message": "Hello {{name}}, your rent of {{amount}} is due tomorrow.",
  "sendAt": "2026-10-01T09:00:00Z",
  "repeat": "monthly",
  "label": "Rent — flat 3",
  "variables": {
    "name": "Aïcha",
    "amount": "45 000 MRU"
  }
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "message": "Hello {{name}}, your rent of {{amount}} is due tomorrow.",
    "sendAt": "2026-10-01T09:00:00Z",
    "repeat": "monthly",
    "label": "Rent — flat 3",
    "variables": {
      "name": "Aïcha",
      "amount": "45 000 MRU"
    }
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/reminders",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "message": "Hello {{name}}, your rent of {{amount}} is due tomorrow.",
        "sendAt": "2026-10-01T09:00:00Z",
        "repeat": "monthly",
        "label": "Rent — flat 3",
        "variables": {
            "name": "Aïcha",
            "amount": "45 000 MRU",
        },
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "reminder": {
    "id": "a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
    "phone": "+22212345678",
    "sendAt": "2026-10-01T09:00:00.000Z",
    "repeat": "monthly",
    "status": "scheduled",
    "…": "…"
  }
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="reminder-import"></a>

#### Importer en masse

`POST /api/v1/reminders/import`

Un seul modèle pour 5000 lignes max (numéro + heure chacune). Les lignes dans le passé sont sautées et listées, pas refusées.

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `message` | string | oui | 1 à 4096 caractères, avec des `{{variables}}` possibles. |
| `repeat` | "none" \| "daily" \| "weekly" \| "monthly" | non | Par défaut `none`. |
| `containerId` | uuid \| null | non | Numéro d'envoi. |
| `rows` | array | oui | 1 à 5000 lignes. |
| `rows[].phone` | string | oui | Numéro international, 8 à 20 caractères (`+22212345678`). Les identifiants `@lid` ne sont pas acceptés ici. |
| `rows[].sendAt` | string (ISO 8601) | oui | Avec `Z` ou un décalage. |
| `rows[].label` | string \| null | non | 255 caractères max. |
| `rows[].variables` | object (string → string) | non | Valeurs de cette ligne. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/reminders/import \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "Hello {{name}}, see you at {{time}}.",
  "rows": [
    {
      "phone": "+22212345678",
      "sendAt": "2026-10-02T08:00:00Z",
      "variables": {
        "name": "Aïcha",
        "time": "10:00"
      }
    },
    {
      "phone": "+22287654321",
      "sendAt": "2026-10-02T08:00:00Z",
      "variables": {
        "name": "Moussa",
        "time": "11:30"
      }
    }
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders/import', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "message": "Hello {{name}}, see you at {{time}}.",
    "rows": [
      {
        "phone": "+22212345678",
        "sendAt": "2026-10-02T08:00:00Z",
        "variables": {
          "name": "Aïcha",
          "time": "10:00"
        }
      },
      {
        "phone": "+22287654321",
        "sendAt": "2026-10-02T08:00:00Z",
        "variables": {
          "name": "Moussa",
          "time": "11:30"
        }
      }
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/reminders/import",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "message": "Hello {{name}}, see you at {{time}}.",
        "rows": [
            {
                "phone": "+22212345678",
                "sendAt": "2026-10-02T08:00:00Z",
                "variables": {
                    "name": "Aïcha",
                    "time": "10:00",
                },
            },
            {
                "phone": "+22287654321",
                "sendAt": "2026-10-02T08:00:00Z",
                "variables": {
                    "name": "Moussa",
                    "time": "11:30",
                },
            },
        ],
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "created": 2,
  "batchId": "e5c1a9f3-2d7b-4b8e-a6f0-8c3d1e5b9a72",
  "skipped": []
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="reminder-update"></a>

#### Modifier un rappel

`PUT /api/v1/reminders/:id`

N'importe quel champ de la création. Un nouveau `sendAt` réactive un rappel terminé, échoué ou annulé.

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant du rappel. |

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | non | Numéro international. |
| `message` | string | non | 1 à 4096 caractères. |
| `sendAt` | string (ISO 8601) | non | Nouvelle heure ; réactive le rappel. |
| `repeat` | "none" \| "daily" \| "weekly" \| "monthly" | non | Nouvelle répétition. |
| `label` | string \| null | non | 255 caractères max. |
| `variables` | object (string → string) | non | Remplace les valeurs. |
| `containerId` | uuid \| null | non | Numéro d'envoi. |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28 \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sendAt": "2026-10-03T09:00:00Z"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "sendAt": "2026-10-03T09:00:00Z"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "sendAt": "2026-10-03T09:00:00Z",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "reminder": {
    "id": "a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
    "sendAt": "2026-10-03T09:00:00.000Z",
    "status": "scheduled",
    "…": "…"
  }
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`FORBIDDEN_ACTION`](https://wahasend.com/docs/fr/errors.md#forbidden_action) (400): This action is not allowed.

<a id="reminder-cancel"></a>

#### Annuler

`POST /api/v1/reminders/:id/cancel`

Arrête un rappel `scheduled` (il reste dans la liste en `cancelled`).

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant du rappel. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28/cancel \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28/cancel', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28/cancel",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "reminder": {
    "id": "a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
    "status": "cancelled",
    "…": "…"
  }
}
```

**Erreurs possibles**

- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`FORBIDDEN_ACTION`](https://wahasend.com/docs/fr/errors.md#forbidden_action) (400): This action is not allowed.

<a id="reminder-delete"></a>

#### Supprimer

`DELETE /api/v1/reminders/:id`

Supprime définitivement le rappel.

- Débloqué par: Notify (`reminders`) + Developer ou Connect pour une clé d'API
- Clé du compte seulement

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `id` | uuid | oui | Identifiant du rappel. |

**Exemple**

curl:

```bash
curl -X DELETE https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28 \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28', {
  method: 'DELETE',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.delete(
    "https://app.wahasend.com/api/v1/reminders/a7e3c5d1-9b2f-4e6a-8c04-5d1f3b7e9a28",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true
}
```

**Erreurs possibles**

- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

---

## Codes à usage unique (OTP)

Envoyez un code de vérification par WhatsApp et vérifiez ce que la personne a tapé. Codes aléatoires, stockés hachés, valables 5 minutes, 3 essais. Inclus dans **Developer** (fonction `otp`).

<a id="otp-send"></a>

#### Envoyer un code

`POST /api/v1/otp/send`

Génère un code et l'envoie tout de suite (sans file d'attente). Gardez `requestId` pour la vérification.

- Débloqué par: Developer (`otp`)
- Clé de numéro : oui

> Des limites, pour que personne ne puisse inonder un numéro via votre application (voir [Limites OTP](https://wahasend.com/docs/fr/errors.md#otp-limits)) : vers un même numéro, un code toutes les 30 secondes, 3 par 10 minutes et 10 par 24 heures ; 60 codes par minute pour toute l'organisation. Au-delà : 429 `OTP_RATE_LIMITED` avec `Retry-After` et `details` `{ scope: "phone" | "organization", reason: "cooldown" | "window" | "day" | "organization", limit, retryAfterSeconds }`. Un code compte dès qu'il est envoyé, même si WhatsApp le refuse ensuite.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Numéro international, 8 à 20 caractères (`+22212345678`). Les identifiants `@lid` ne sont pas acceptés ici. |
| `template` | string | non | Votre texte, 500 caractères max. Doit contenir `{{code}}` (sinon 400 `OTP_TEMPLATE_MISSING_CODE`) : chaque `{{code}}` devient le code et chaque `{{expiry}}` les minutes. Par défaut : « Your verification code is: *{{code}}* … ». |
| `length` | integer | non | Chiffres, 4 à 8. Par défaut 6. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/otp/send \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678",
  "template": "Your Kaba Shop code: {{code}}. It expires in {{expiry}} minutes.",
  "length": 6
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/otp/send', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678",
    "template": "Your Kaba Shop code: {{code}}. It expires in {{expiry}} minutes.",
    "length": 6
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/otp/send",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
        "template": "Your Kaba Shop code: {{code}}. It expires in {{expiry}} minutes.",
        "length": 6,
    },
)
print(r.json())
```

**Réponse 201**

```json
{
  "success": true,
  "requestId": "0b8e4d2a-7c1f-4a95-b3e6-9d2c5f8a1e47",
  "phone": "+22212345678",
  "expiresAt": "2026-09-30T10:05:00.000Z",
  "expiresInSeconds": 300
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`OTP_TEMPLATE_MISSING_CODE`](https://wahasend.com/docs/fr/errors.md#otp_template_missing_code) (400): The message template must contain {{code}}, where the code goes.
- [`OTP_RATE_LIMITED`](https://wahasend.com/docs/fr/errors.md#otp_rate_limited) (429): Too many verification codes for this number or this account. Wait before sending another one.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.

<a id="otp-verify"></a>

#### Vérifier un code

`POST /api/v1/otp/verify`

Répond `verified: true` une seule fois. Un mauvais code consomme un des 3 essais.

- Débloqué par: Developer (`otp`)
- Clé de numéro : oui

> Un mauvais code répond 400 `OTP_INVALID_CODE` avec `details.attemptsRemaining`.

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `requestId` | uuid | oui | Celui de l'envoi. |
| `code` | string | oui | Ce que la personne a tapé, 4 à 8 caractères. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/otp/verify \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "requestId": "0b8e4d2a-7c1f-4a95-b3e6-9d2c5f8a1e47",
  "code": "482913"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/otp/verify', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "requestId": "0b8e4d2a-7c1f-4a95-b3e6-9d2c5f8a1e47",
    "code": "482913"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/otp/verify",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "requestId": "0b8e4d2a-7c1f-4a95-b3e6-9d2c5f8a1e47",
        "code": "482913",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "verified": true,
  "phone": "+22212345678"
}
```

**Erreurs possibles**

- [`OTP_INVALID_CODE`](https://wahasend.com/docs/fr/errors.md#otp_invalid_code) (400): Invalid code.
- [`OTP_EXPIRED`](https://wahasend.com/docs/fr/errors.md#otp_expired) (410): OTP has expired. Request a new code.
- [`OTP_MAX_ATTEMPTS`](https://wahasend.com/docs/fr/errors.md#otp_max_attempts) (429): Maximum verification attempts exceeded. Request a new code.
- [`OTP_ALREADY_VERIFIED`](https://wahasend.com/docs/fr/errors.md#otp_already_verified) (400): OTP has already been verified.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

---

## Conversations et contacts

Lisez ce que votre numéro a envoyé et reçu, une conversation par personne, et cherchez des contacts. Inclus dans **Connect** (fonction `inbox`). Une personne est identifiée par les chiffres de son numéro, ou par son identifiant `@lid` quand WhatsApp cache le numéro.

<a id="chats-list"></a>

#### Lister les conversations

`GET /api/v1/chats`

Les plus récentes d'abord, avec le dernier message et le nombre de non-lus.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

> Une conversation connue seulement par son identifiant a `phone: "…@lid"` ; quand les deux sont connus, `lid` est ajouté. Un dernier message avec fichier ajoute `lastAttachment`, p. ex. `"PDF · invoice.pdf · 240 KB"`.

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `limit` | number | non | Par défaut 50, 200 conversations max (les valeurs jusqu'à 500 sont acceptées et donnent 200). |
| `search` | string | non | Partie d'un numéro ou d'un nom. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl "https://app.wahasend.com/api/v1/chats?limit=20" \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/chats?limit=20', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/chats?limit=20",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "chats": [
    {
      "phone": "22212345678",
      "name": "Aïcha",
      "lastMessage": "Merci, à demain",
      "lastDirection": "in",
      "lastAt": "2026-09-30T10:41:07.000Z",
      "unread": 1
    }
  ]
}
```

<a id="chats-thread"></a>

#### Lire une conversation

`GET /api/v1/chats/:phone`

Les derniers messages avec une personne, les plus anciens d'abord. Les messages envoyés ont leur `status` de remise.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Chiffres du numéro (avec ou sans `+`) ou l'identifiant `@lid`, encodé pour l'URL. |

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `limit` | number | non | Par défaut 50, 500 max. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/chats/22212345678 \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/chats/22212345678', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/chats/22212345678",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "phone": "22212345678",
  "name": "Aïcha",
  "messages": [
    {
      "id": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
      "direction": "out",
      "text": "Your order #1042 is ready for pickup.",
      "at": "2026-09-30T10:30:12.000Z",
      "status": "read"
    },
    {
      "id": "9a3e1b77-0c52-4f8e-b1d4-2e6c9f0a7b13",
      "direction": "in",
      "text": "Merci, à demain",
      "at": "2026-09-30T10:41:07.000Z",
      "type": "chat"
    }
  ]
}
```

<a id="chats-unread"></a>

#### Messages non lus

`GET /api/v1/chats/unread`

Messages reçus que personne n'a marqués lus, les plus anciens d'abord. Ne les marque pas.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `limit` | number | non | Par défaut 100, 500 max. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/chats/unread \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/chats/unread', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/chats/unread",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "messages": [
    {
      "id": "9a3e1b77-0c52-4f8e-b1d4-2e6c9f0a7b13",
      "phone": "22212345678",
      "name": "Aïcha",
      "text": "Merci, à demain",
      "type": "chat",
      "at": "2026-09-30T10:41:07.000Z"
    }
  ]
}
```

<a id="chats-ack"></a>

#### Marquer des messages lus

`POST /api/v1/chats/unread/ack`

Marque les messages reçus donnés comme lus dans WahaSend (le contact ne voit pas de coches bleues). Une clé de numéro ne marque que les messages de son numéro.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `ids` | uuid[] | oui | 1 à 500 identifiants de la liste des non-lus. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/chats/unread/ack \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "9a3e1b77-0c52-4f8e-b1d4-2e6c9f0a7b13"
  ]
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/chats/unread/ack', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "ids": [
      "9a3e1b77-0c52-4f8e-b1d4-2e6c9f0a7b13"
    ]
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/chats/unread/ack",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "ids": [
            "9a3e1b77-0c52-4f8e-b1d4-2e6c9f0a7b13",
        ],
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "marked": 1
}
```

<a id="chats-read"></a>

#### Marquer une conversation lue

`POST /api/v1/chats/:phone/read`

Marque comme lu tout ce qui a été reçu de cette personne (avec une clé de numéro : sur son numéro seulement).

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Chiffres du numéro ou identifiant `@lid`. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/chats/22212345678/read \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/chats/22212345678/read', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/chats/22212345678/read",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "marked": 3
}
```

<a id="contacts-search"></a>

#### Chercher des contacts

`GET /api/v1/contacts`

Cherche dans les contacts du téléphone par nom enregistré, nom WhatsApp ou partie du numéro.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `search` | string | oui | 1 à 100 caractères. |
| `limit` | number | non | Par défaut 10, 50 max. |

**Exemple**

curl:

```bash
curl "https://app.wahasend.com/api/v1/contacts?search=A%C3%AFcha" \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/contacts?search=A%C3%AFcha', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/contacts?search=A%C3%AFcha",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "contacts": [
    {
      "id": "5e1b7c3a-9d2f-4a86-b0e4-1c7f9a3d5b62",
      "name": "Aïcha",
      "phone": "22212345678",
      "lid": "104937261508834@lid",
      "saved": true,
      "optedOut": false
    }
  ]
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.

<a id="contacts-count"></a>

#### Compter les contacts

`GET /api/v1/contacts/count`

Combien de contacts sont connus, et quand l'historique récent du téléphone a été importé.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/contacts/count \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/contacts/count', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/contacts/count",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "count": 312,
  "historyImportedAt": "2026-09-29T18:03:55.000Z",
  "importing": false
}
```

<a id="contacts-sync"></a>

#### Mettre à jour depuis le téléphone

`POST /api/v1/contacts/sync`

Relit le carnet d'adresses maintenant et importe les conversations récentes en arrière-plan. Le numéro doit être connecté.

- Débloqué par: Connect (`inbox`)
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/contacts/sync \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/contacts/sync', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/contacts/sync",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "contacts": 312,
  "saved": 240,
  "lidsLinked": 118,
  "messagesLinked": 6,
  "importing": true
}
```

**Erreurs possibles**

- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`WAHA_SESSION_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_session_error) (503): WhatsApp is not connected.

---

## Numéros WhatsApp

Vérifiez et reliez vos numéros depuis le code. Tous les produits y ont accès. Une clé de numéro ne voit que son numéro ; avec la clé du compte, choisissez-en un avec `containerId` (voir `GET /session/list`). L'ajout et la suppression de numéros se font dans le tableau de bord. Notify, Assistant et Shop ajoutent autant de numéros que nécessaire. Avec Developer ou Connect actif, le compte a 3 numéros inclus, plus les numéros supplémentaires qu'il achète (10 $ par mois chacun, depuis la page Mon abonnement du tableau de bord) ; en ajouter un de plus répond 403 `NUMBER_LIMIT_REACHED`. Les numéros que vous avez déjà ne sont jamais supprimés : un compte au-delà de son allocation continue d'envoyer depuis tous.

<a id="session-list"></a>

#### Lister vos numéros

`GET /api/v1/session/list`

Tous les numéros de l'organisation avec leur état en direct. `id` est le `containerId` des autres appels.

- Débloqué par: tous les produits
- Clé du compte seulement

> Avec seulement Notify, Assistant ou Shop, il n'y a pas de limite : `maxSessions` vaut `null` et `canCreate` vaut `true`. Avec Developer ou Connect actif, `maxSessions` correspond aux 3 numéros inclus plus les numéros supplémentaires achetés (`allowance.included` + `allowance.extra`, `allowance.capped: true`). Sans produit actif, `maxSessions` vaut `0`.

> `allowance.over` compte les numéros au-delà de l'allocation (ajoutés avant, ou numéros supplémentaires terminés) : ils continuent tous de fonctionner, et `canCreate` reste `false` tant que vous n'achetez pas de numéros supplémentaires ou n'en retirez pas. Un nouveau numéro au-delà de l'allocation répond 403 `NUMBER_LIMIT_REACHED` avec `details: {max, used, included, extra, capped}`.

> Cette réponse (et celles des autres routes de numéros) n'a pas de champ `success`.

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/list \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/list', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/list",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "sessions": [
    {
      "id": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
      "instanceName": "waha-pool-1",
      "label": "Shop",
      "phoneNumber": "22212345678",
      "status": "WORKING",
      "isDefault": true,
      "connected": true,
      "me": {
        "id": "22212345678@c.us",
        "pushName": "Kaba Shop"
      },
      "hasApiKey": true,
      "apiKeyPreview": "waha_V1StG...",
      "createdAt": "2026-09-01T12:00:00.000Z"
    }
  ],
  "maxSessions": 3,
  "usedSessions": 1,
  "canCreate": true,
  "allowance": {
    "capped": true,
    "included": 3,
    "extra": 0,
    "extraExpiresAt": null,
    "used": 1,
    "max": 3,
    "over": 0
  }
}
```

<a id="session-send-group"></a>

#### Plusieurs numéros : le réglage

`GET /api/v1/session/send-group`

Si les envois qui ne nomment pas de numéro passent par un groupe de vos numéros, et comment. Désactivé par défaut. Propriétaire et admins. Voir [Plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers).

- Débloqué par: tous les produits
- Clé du compte seulement

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/send-group \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/send-group', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/send-group",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "sendGroup": {
    "enabled": true,
    "containerIds": [
      "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
      "5d1e8a2c-7b3f-4c69-a0d4-2e9f1b7c6a53"
    ],
    "mode": "backup",
    "sticky": true
  }
}
```

<a id="session-send-group-update"></a>

#### Plusieurs numéros : le changer

`PUT /api/v1/session/send-group`

N'importe lesquels des champs ; les autres restent. Propriétaire ou admin connecté seulement (pas une clé d'API) ; chaque changement est noté dans le journal d'activité.

- Débloqué par: tous les produits
- Clé du compte seulement

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `enabled` | boolean | non | Activer ou désactiver l'envoi par plusieurs numéros. |
| `containerIds` | uuid[] | non | Les numéros du groupe, dans l'ordre (20 max, les vôtres). Obligatoire quand c'est activé. |
| `mode` | "backup" \| "share" | non | `backup` (par défaut) : le numéro par défaut, le suivant quand il tombe. `share` : répartir sur le groupe. |
| `sticky` | boolean | non | Même contact, même numéro (par défaut `true`). |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/session/send-group \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": true,
  "containerIds": [
    "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
    "5d1e8a2c-7b3f-4c69-a0d4-2e9f1b7c6a53"
  ],
  "mode": "backup",
  "sticky": true
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/send-group', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "enabled": true,
    "containerIds": [
      "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
      "5d1e8a2c-7b3f-4c69-a0d4-2e9f1b7c6a53"
    ],
    "mode": "backup",
    "sticky": true
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/session/send-group",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "enabled": True,
        "containerIds": [
            "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
            "5d1e8a2c-7b3f-4c69-a0d4-2e9f1b7c6a53",
        ],
        "mode": "backup",
        "sticky": True,
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "sendGroup": {
    "enabled": true,
    "containerIds": [
      "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
      "5d1e8a2c-7b3f-4c69-a0d4-2e9f1b7c6a53"
    ],
    "mode": "backup",
    "sticky": true
  }
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.
- [`USER_REQUIRED`](https://wahasend.com/docs/fr/errors.md#user_required) (403): Sign in to the dashboard to do this; API keys cannot.

<a id="session-status"></a>

#### État de la connexion

`GET /api/v1/session/status`

`status` vaut `WORKING` quand le numéro peut envoyer ; `action` dit quoi faire sinon.

- Débloqué par: tous les produits
- Clé de numéro : oui

> Autres états : `SCAN_QR_CODE` (à relier), `STARTING`, `STOPPED`, `FAILED`, `UNREACHABLE`.

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/status \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/status', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/status",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "session": "n6d2b9c1e8f4a4e37",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
  "status": "WORKING",
  "connected": true,
  "me": {
    "id": "22212345678@c.us",
    "pushName": "Kaba Shop"
  },
  "engine": "GOWS",
  "manager": {
    "retryCount": 0,
    "maxRetries": 5,
    "queuesPaused": false,
    "lastHealthy": "2026-09-30T10:40:00.000Z",
    "connectedSince": "2026-09-28T07:12:00.000Z",
    "uptime": "2d 3h",
    "totalRecoveries": 1,
    "totalFailures": 1,
    "lastError": null,
    "lastErrorAt": null
  },
  "action": {
    "type": "connected",
    "message": "Session is connected and operational. All messaging features are available.",
    "canLogout": true
  }
}
```

**Erreurs possibles**

- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.
- [`NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#not_found) (404): Not found.

<a id="session-qr"></a>

#### QR code pour relier

`GET /api/v1/session/qr`

Tant que l'état est `SCAN_QR_CODE` : un PNG à scanner dans WhatsApp → Réglages → Appareils connectés → Connecter un appareil. Il change environ chaque minute.

- Débloqué par: tous les produits
- Clé de numéro : oui

> Déjà relié : `{ "status": "already_connected" }`. Autre état : `{ "status": "STARTING", "message", "hint" }`.

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `format` | "raw" | non | `raw` renvoie le texte du QR au lieu d'une image. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/qr \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/qr', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/qr",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "status": "SCAN_QR_CODE",
  "qr": {
    "image": "data:image/png;base64,iVBORw0KGgo…",
    "expiresInSeconds": 60,
    "message": "Scan this QR code with your WhatsApp app. Open WhatsApp > Settings > Linked Devices > Link a Device."
  }
}
```

**Erreurs possibles**

- [`WAHA_UNREACHABLE`](https://wahasend.com/docs/fr/errors.md#waha_unreachable) (503): WhatsApp service is unreachable. Please try again later.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

<a id="session-pair"></a>

#### Code d'association (sans QR)

`POST /api/v1/session/pair`

Obtient un code de 8 caractères à taper dans WhatsApp → Appareils connectés → Connecter avec le numéro de téléphone. Pratique quand le QR est sur le même téléphone.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `phone` | string | oui | Le numéro à relier, format international. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/session/pair \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "phone": "+22212345678"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/pair', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "phone": "+22212345678"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/session/pair",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "phone": "+22212345678",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "status": "code_sent",
  "code": "ABCD-1234",
  "message": "A pairing code has been generated. Enter it in your WhatsApp app: Settings > Linked Devices > Link a Device > Link with phone number instead."
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`WAHA_UNREACHABLE`](https://wahasend.com/docs/fr/errors.md#waha_unreachable) (503): WhatsApp service is unreachable. Please try again later.
- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.

<a id="session-start"></a>

#### Démarrer

`POST /api/v1/session/start`

Démarre un numéro arrêté. Puis récupérez le QR ou un code d'association s'il en demande un.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/session/start \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/start', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/session/start",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "status": "starting",
  "message": "Session is starting. Use GET /api/v1/session/qr to retrieve the QR code once ready, or POST /api/v1/session/pair for a pairing code."
}
```

**Erreurs possibles**

- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.
- [`NO_SESSION_CONFIGURED`](https://wahasend.com/docs/fr/errors.md#no_session_configured) (400): This account has no connected WhatsApp number.

<a id="session-reconnect"></a>

#### Reconnecter

`POST /api/v1/session/reconnect`

Redémarre la connexion d'un numéro bloqué. Le lien avec le téléphone est conservé.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/session/reconnect \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/reconnect', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/session/reconnect",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "message": "Restart initiated. Waiting for session to connect..."
}
```

**Erreurs possibles**

- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.

<a id="session-logout"></a>

#### Délier

`POST /api/v1/session/logout`

Déconnecte le numéro de WhatsApp. Pour le relier, il faudra un nouveau QR ou code.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/session/logout \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/logout', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/session/logout",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "status": "logged_out",
  "message": "WhatsApp session has been disconnected. You will need to scan a new QR code to reconnect."
}
```

**Erreurs possibles**

- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.

<a id="session-history"></a>

#### Historique de connexion

`GET /api/v1/session/history`

Derniers changements d'état du numéro, les plus récents d'abord.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `limit` | number | non | Par défaut 20, 100 max. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/history \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/history', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/history",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "session": "n6d2b9c1e8f4a4e37",
  "events": [
    {
      "id": "b3f1…",
      "status": "WORKING",
      "previousStatus": "STARTING",
      "reason": null,
      "autoRecovery": false,
      "metadata": null,
      "timestamp": "2026-09-28T07:12:00.000Z"
    }
  ]
}
```

<a id="session-profile-get"></a>

#### Lire le profil

`GET /api/v1/session/profile`

Le nom WhatsApp, le texte « à propos » et la photo du numéro.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/session/profile \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/profile', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/session/profile",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "session": "n6d2b9c1e8f4a4e37",
  "profile": {
    "id": "22212345678@c.us",
    "name": "Kaba Shop",
    "status": "Open 9:00–18:00",
    "picture": "https://pps.whatsapp.net/…"
  }
}
```

**Erreurs possibles**

- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.

<a id="session-profile-update"></a>

#### Changer le profil

`PUT /api/v1/session/profile`

Nom, texte « à propos » et photo, au choix. Chacun est tenté séparément ; `failed` liste ce que WhatsApp a refusé.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `name` | string | non | 1 à 25 caractères. |
| `status` | string | non | Texte « à propos », 139 caractères max. |
| `pictureUrl` | string (URL) | non | Lien public vers une image carrée. |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/session/profile \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Kaba Shop",
  "status": "Open 9:00–18:00"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/profile', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "name": "Kaba Shop",
    "status": "Open 9:00–18:00"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/session/profile",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "name": "Kaba Shop",
        "status": "Open 9:00–18:00",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "updated": {
    "name": "updated",
    "status": "updated"
  }
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.

<a id="session-profile-picture-delete"></a>

#### Retirer la photo

`DELETE /api/v1/session/profile/picture`

Retire la photo de profil.

- Débloqué par: tous les produits
- Clé de numéro : oui

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | non | Lequel de vos numéros WhatsApp utiliser ; il l'emporte toujours. Par défaut : votre numéro par défaut (sinon un numéro connecté), ou un numéro du groupe quand [plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) est activé. Ignoré avec une clé de numéro, qui utilise toujours son numéro. |

**Exemple**

curl:

```bash
curl -X DELETE https://app.wahasend.com/api/v1/session/profile/picture \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/profile/picture', {
  method: 'DELETE',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.delete(
    "https://app.wahasend.com/api/v1/session/profile/picture",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "status": "deleted",
  "message": "Profile picture removed."
}
```

**Erreurs possibles**

- [`WAHA_ERROR`](https://wahasend.com/docs/fr/errors.md#waha_error) (502): WhatsApp could not complete the request. Try again.

---

## Compte, historique et clés

Qui vous êtes, ce qui a été envoyé, et vos clés. Tous les produits y ont accès et peuvent créer des clés d'API (voir [Quels produits utilisent l'API](https://wahasend.com/docs/fr/authentication.md)).

<a id="me"></a>

#### Votre compte

`GET /api/v1/me`

L'organisation, ses produits et fonctions actifs, et si une clé existe. Un moyen rapide de tester une clé.

- Débloqué par: tous les produits
- Clé du compte seulement

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/me \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/me', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/me",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "id": "2c7d0e19-4b8a-4f63-9e21-7a5c3d9b1f08",
  "name": "Aminetou Sall",
  "email": "aminetou@example.com",
  "memberRole": "admin",
  "organization": {
    "id": "2c7d0e19-4b8a-4f63-9e21-7a5c3d9b1f08",
    "name": "Kaba Shop",
    "requireTwoFactor": false
  },
  "products": [
    "developer"
  ],
  "features": [
    "send",
    "batch",
    "api",
    "otp",
    "media",
    "webhooks"
  ],
  "maxNumbers": null,
  "webhookUrl": "https://example.com/hooks/wahasend",
  "hasApiKey": true,
  "apiKeyPreview": "3f9c1a7b2e4d***",
  "…": "…"
}
```

<a id="messages"></a>

#### Historique des messages

`GET /api/v1/messages`

Messages partis par envois texte, lots, campagnes, OTP, rappels et assistants, les plus récents d'abord.

- Débloqué par: tous les produits
- Clé du compte seulement

> Les messages OTP affichent `[OTP MESSAGE]` au lieu du code. Les envois de médias apparaissent avec leur légende ou une étiquette comme `[image] https://…`.

> `wahaMessageId` est l'identifiant WhatsApp une fois le message parti (null avant) ; `containerId` est le numéro d'envoi (null sur les anciennes lignes).

**Paramètres de requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `type` | "alert" \| "bulk" \| "otp" | non | Filtrer par type. |
| `status` | string | non | `queued`, `sending`, `sent`, `delivered`, `read` ou `failed`. |
| `search` | string | non | Partie du destinataire ou du texte. |
| `page` | number | non | Par défaut 1. |
| `limit` | number | non | Par défaut 50, 100 max. |

**Exemple**

curl:

```bash
curl "https://app.wahasend.com/api/v1/messages?status=failed&limit=20" \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/messages?status=failed&limit=20', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/messages?status=failed&limit=20",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "total": 1,
  "page": 1,
  "limit": 20,
  "messages": [
    {
      "id": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
      "type": "alert",
      "recipient": "22212345678",
      "content": "Your order #1042 is ready for pickup.",
      "status": "failed",
      "priority": "normal",
      "error": "Message delivery failed (ACK 0)",
      "sentAt": null,
      "deliveredAt": null,
      "createdAt": "2026-09-30T10:30:10.000Z",
      "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
      "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64"
    }
  ]
}
```

<a id="stats"></a>

#### Compteurs

`GET /api/v1/stats`

Totaux de tous les messages, par statut et par type, plus les dernières 24 heures et 7 jours.

- Débloqué par: tous les produits
- Clé du compte seulement

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/stats \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/stats', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/stats",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "totalMessages": 1247,
  "sent": 30,
  "delivered": 1150,
  "failed": 47,
  "alerts": 900,
  "bulk": 320,
  "otp": 27,
  "last24h": 58,
  "last7d": 412
}
```

<a id="regenerate-key"></a>

#### Remplacer la clé du compte

`POST /api/v1/me/regenerate-key`

Crée une nouvelle clé de compte ; l'ancienne cesse de fonctionner aussitôt. `POST /me/generate-key` fait de même la première fois (409 `CONFLICT` si une clé existe déjà).

- Débloqué par: tous les produits (`api`)
- Clé du compte seulement

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/me/regenerate-key \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/me/regenerate-key', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/me/regenerate-key",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "apiKey": "wn_8c1f…64 hex characters…",
  "warning": "Save this new API key now. Your old key has been invalidated."
}
```

**Erreurs possibles**

- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="regenerate-number-key"></a>

#### Remplacer une clé de numéro

`POST /api/v1/session/:containerId/regenerate-key`

Nouvelle clé pour un numéro ; l'ancienne cesse de fonctionner. `POST /session/:containerId/generate-key` crée la première.

- Débloqué par: tous les produits (`api`)
- Clé du compte seulement

**Paramètres de chemin**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `containerId` | uuid | oui | Le numéro, voir `GET /session/list`. |

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/session/6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64/regenerate-key \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/session/6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64/regenerate-key', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/session/6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64/regenerate-key",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "apiKey": "waha_V1StGXR8_Z5jdHi6B-myT…",
  "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
  "label": "Shop",
  "warning": "Store this key securely. It cannot be retrieved again."
}
```

**Erreurs possibles**

- [`CONTAINER_NOT_FOUND`](https://wahasend.com/docs/fr/errors.md#container_not_found) (404): WhatsApp number not found.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

---

## Webhooks

WahaSend envoie des événements à une URL à vous : réponses des clients, accusés de remise, numéro déconnecté, assistant qui a besoin d'une personne, nouvelle commande. **Developer** reçoit tous les événements (fonction `webhooks`). Les propriétaires d'**Assistant** et de **Shop** peuvent aussi définir un webhook et reçoivent les événements de leur produit : `bot.needs_review` et `bot.handoff`, plus `commerce.order` avec Shop. Réglez l'adresse dans le tableau de bord (**Pour les développeurs** → URL de webhook) ou par l'API :

<a id="webhook-set"></a>

##### Définir ou effacer l'URL du webhook

`PUT /api/v1/me/webhook`

Une URL par organisation. `null` arrête les événements. La première URL crée aussi le secret de signature, renvoyé une fois ici dans `signingSecret` (relisez-le avec `GET /me/webhook`).

- Débloqué par: Assistant, Shop, Developer (`webhooks | bots`)
- Clé du compte seulement

**Corps de la requête**

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `webhookUrl` | string (URL) \| null | oui | Adresse http(s) publique, 2000 caractères max. Les adresses privées ou locales sont refusées. |

**Exemple**

curl:

```bash
curl -X PUT https://app.wahasend.com/api/v1/me/webhook \
  -H "X-API-Key: $WAHASEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "webhookUrl": "https://example.com/hooks/wahasend-7Qe2xLp9"
}'
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/me/webhook', {
  method: 'PUT',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "webhookUrl": "https://example.com/hooks/wahasend-7Qe2xLp9"
  }),
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.put(
    "https://app.wahasend.com/api/v1/me/webhook",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
    json={
        "webhookUrl": "https://example.com/hooks/wahasend-7Qe2xLp9",
    },
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "webhookUrl": "https://example.com/hooks/wahasend-7Qe2xLp9",
  "signingSecret": "whsec_5f0c…64 hex characters…"
}
```

**Erreurs possibles**

- [`VALIDATION_ERROR`](https://wahasend.com/docs/fr/errors.md#validation_error) (400): Validation failed. Check the details for specific field errors.
- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.

<a id="webhook-get"></a>

##### URL du webhook et secret de signature

`GET /api/v1/me/webhook`

L'URL et le secret pour [vérifier les signatures](https://wahasend.com/docs/fr/webhooks.md#webhook-security). Propriétaires et admins seulement.

- Débloqué par: Assistant, Shop, Developer (`webhooks | bots`)
- Clé du compte seulement

> `signingSecret` vaut null tant qu'aucune URL n'a été définie (ou sur un serveur sans chiffrement configuré : les envois ne sont alors pas signés).

**Exemple**

curl:

```bash
curl https://app.wahasend.com/api/v1/me/webhook \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/me/webhook', {
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.get(
    "https://app.wahasend.com/api/v1/me/webhook",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "webhookUrl": "https://example.com/hooks/wahasend-7Qe2xLp9",
  "signingSecret": "whsec_5f0c…64 hex characters…"
}
```

**Erreurs possibles**

- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.
- [`ROLE_FORBIDDEN`](https://wahasend.com/docs/fr/errors.md#role_forbidden) (403): Your role in this organization does not allow this. Ask an owner or admin.

<a id="webhook-secret-regenerate"></a>

##### Remplacer le secret de signature

`POST /api/v1/me/webhook/regenerate-secret`

Un nouveau secret ; les envois sont signés avec lui aussitôt et l'ancien ne vérifie plus. Mettez votre récepteur à jour juste après.

- Débloqué par: Assistant, Shop, Developer (`webhooks | bots`)
- Clé du compte seulement

**Exemple**

curl:

```bash
curl -X POST https://app.wahasend.com/api/v1/me/webhook/regenerate-secret \
  -H "X-API-Key: $WAHASEND_API_KEY"
```

JavaScript:

```js
const res = await fetch('https://app.wahasend.com/api/v1/me/webhook/regenerate-secret', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.WAHASEND_API_KEY },
});
const data = await res.json();
```

Python:

```python
import os
import requests

r = requests.post(
    "https://app.wahasend.com/api/v1/me/webhook/regenerate-secret",
    headers={"X-API-Key": os.environ["WAHASEND_API_KEY"]},
)
print(r.json())
```

**Réponse 200**

```json
{
  "success": true,
  "signingSecret": "whsec_91ab…64 hex characters…",
  "warning": "Update your receiver with this secret now. Deliveries are signed with it from now on."
}
```

**Erreurs possibles**

- [`PRODUCT_REQUIRED`](https://wahasend.com/docs/fr/errors.md#product_required) (403): This feature is not included in your products.
- [`WEBHOOK_SIGNING_UNAVAILABLE`](https://wahasend.com/docs/fr/errors.md#webhook_signing_unavailable) (503): Webhook signing is not available on this server yet (SECRETS_ENCRYPTION_KEY is not set).

<a id="webhook-delivery"></a>

#### Comment les événements arrivent

- `POST` avec `Content-Type: application/json` et `User-Agent: WahaSend/1.0`, corps `{ event, data, timestamp }`.
- En-têtes : `X-WahaSend-Event` (le nom de l'événement), `X-WahaSend-Delivery` (un identifiant par événement, le même à chaque nouvelle tentative : il permet d'écarter les doublons) et `X-WahaSend-Signature` ([à vérifier](https://wahasend.com/docs/fr/webhooks.md#webhook-security)).
- Répondez un 2xx en moins de 10 secondes. Sinon (ou délai dépassé, ou redirection : elles ne sont pas suivies) l'envoi est retenté : 5 tentatives en tout, espacées d'environ 5 s, 10 s, 20 s et 40 s. Ensuite l'événement est abandonné.
- Les événements partent vers l'URL réglée au moment de l'envoi ; une adresse privée ou locale est refusée là aussi.
- L'ordre n'est pas garanti et un événement peut arriver deux fois : rendez votre traitement idempotent (avec `messageId` / `wahaMessageId`).

Chaque événement:

```json
{
  "event": "message.delivered",
  "data": { … },
  "timestamp": "2026-09-30T10:30:15.204Z"
}
```

<a id="webhook-security"></a>

#### Vérifier que c'est nous

Chaque événement est signé avec le **secret de signature** de votre organisation (`whsec_…`, créé avec votre première URL de webhook ; visible dans le tableau de bord ou avec `GET /me/webhook`). L'en-tête ressemble à ceci :

```http
X-WahaSend-Signature: t=1790762475,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

1. Lisez `t` (secondes Unix) et `v1` dans l'en-tête.
2. Calculez le HMAC-SHA256 avec votre secret sur `t + "." + body`, body étant le corps **brut** de la requête tel que reçu (pas du JSON re-sérialisé). Encodez-le en hexadécimal.
3. Comparez-le à `v1` en temps constant. Refusez l'événement s'ils diffèrent.
4. Refusez-le aussi si `t` est à plus de 5 minutes de votre horloge : un ancien événement rejoué par quelqu'un d'autre échoue alors.

Node.js (Express):

```js
const crypto = require('crypto');
const express = require('express');
const app = express();
const SECRET = process.env.WAHASEND_WEBHOOK_SECRET; // whsec_…

function verify(rawBody, header, toleranceSeconds = 300) {
  const parts = Object.fromEntries((header || '').split(',').map(p => p.split('=')));
  const t = Number(parts.t);
  if (!t || !parts.v1 || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(parts.v1, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// express.raw keeps the body as bytes: sign-check those, then parse
app.post('/hooks/wahasend', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verify(raw, req.get('X-WahaSend-Signature'))) return res.status(400).end();
  const { event, data } = JSON.parse(raw);
  // … handle it (dedupe on req.get('X-WahaSend-Delivery'))
  res.sendStatus(200);
});
```

Python (Flask):

```python
import hashlib, hmac, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["WAHASEND_WEBHOOK_SECRET"].encode()  # whsec_…

def verify(raw_body: bytes, header: str, tolerance_seconds: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in (header or "").split(",") if "=" in p)
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False
    if abs(time.time() - t) > tolerance_seconds:
        return False
    expected = hmac.new(SECRET, f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

@app.post("/hooks/wahasend")
def wahasend():
    raw = request.get_data()  # the raw bytes, before any JSON parsing
    if not verify(raw, request.headers.get("X-WahaSend-Signature", "")):
        abort(400)
    event = request.get_json()
    # … handle it (dedupe on request.headers["X-WahaSend-Delivery"])
    return "", 200
```

> Remplacez le secret avec **Nouvelle clé** dans le tableau de bord (ou `POST /me/webhook/regenerate-secret`) : les événements sont signés avec le nouveau aussitôt, mettez donc votre récepteur à jour juste après. Gardez une URL en HTTPS.

<a id="webhook-events"></a>

#### Événements

| Événement | Quand | Qui le reçoit |
| --- | --- | --- |
| `message.inbound` | Quelqu'un a écrit à votre numéro. | Developer |
| `message.delivered` · `message.read` · `message.failed` | Un message parti par envoi texte, envoi de média, lot, campagne, OTP, rappel ou assistant a changé de statut. (`message.sent` est rare : les envois sont déjà marqués envoyés.) | Developer |
| `poll.vote` | Quelqu'un a voté à un sondage envoyé. | Developer |
| `session.needs_scan` · `session.disconnected` · `session.recovered` | Un numéro doit être relié à nouveau, est tombé, ou est revenu. | Developer |
| `bot.needs_review` · `bot.handoff` | Un assistant n'a pas pu répondre, ou a passé la main à une personne. | Assistant, Shop ou Developer |
| `commerce.order` | Un client a passé une commande (paiement à la livraison) via l'assistant boutique. | Shop ou Developer |

message.inbound:

```json
{
  "event": "message.inbound",
  "data": {
    "sender": "22212345678",
    "senderName": "Aïcha",
    "body": "Is the blue dress still available?",
    "messageType": "chat",
    "wahaMessageId": "false_22212345678@c.us_3A1F9C2B7E",
    "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
    "numberPhone": "22200000000",
    "timestamp": "2026-09-30T10:41:07.512Z"
  },
  "timestamp": "2026-09-30T10:41:07.790Z"
}
```

- `sender` est le numéro en chiffres, ou l'identifiant `…@lid` quand WhatsApp cache le numéro.
- Champs facultatifs : `isGroup` + `groupId` (messages de groupe), `latitude` + `longitude` (une position, `messageType: "location"`), `selectedButtonId`, `selectedRowId`.
- Les messages sans texte (photo sans légende, sticker) ne donnent pas d'événement. Une note vocale n'arrive que sur les comptes avec un assistant, sous forme de transcription (`messageType: "voice"`).
- `containerId` et `numberPhone` disent lequel de vos numéros l'a reçu (`numberPhone` vaut null pour un numéro pas encore relié).

message.delivered (aussi .read, .failed):

```json
{
  "event": "message.delivered",
  "data": {
    "messageId": "4f0c6a1e-2b7d-4c1a-9e35-7d8f2a6b1c90",
    "recipient": "22212345678",
    "type": "alert",
    "status": "delivered",
    "wahaMessageId": "true_22212345678@c.us_3EB0C767D26A1D2E5A8F",
    "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
    "numberPhone": "22200000000",
    "timestamp": "2026-09-30T10:30:15.101Z"
  },
  "timestamp": "2026-09-30T10:30:15.204Z"
}
```

`type` vaut `alert` (envois texte et médias, lots, rappels), `bulk` (campagnes, avec `campaignId` en plus), `otp` ou `bot_reply`. `containerId` / `numberPhone` désignent le numéro d'envoi (null pour les messages d'avant leur enregistrement).

session.disconnected:

```json
{
  "event": "session.disconnected",
  "data": {
    "status": "FAILED",
    "message": "Your WhatsApp number has disconnected and automatic recovery failed. Please reconnect it from your dashboard.",
    "retriesExhausted": true,
    "lastAttempt": "2026-09-30T03:12:40.000Z",
    "containerId": "6d2b9c1e-8f4a-4e37-a5c2-1b9e7d3f0a64",
    "numberPhone": "22200000000",
    "session": "n6d2b9c1e8f4a4e37",
    "timestamp": "2026-09-30T03:12:40.020Z"
  },
  "timestamp": "2026-09-30T03:12:40.300Z"
}
```

Quand le serveur WhatsApp qui héberge le numéro est en panne, `session.disconnected` porte `"reason": "instance_down"` au lieu de `retriesExhausted` ; le numéro revient tout seul. `session.needs_scan` porte `"status": "SCAN_QR_CODE"` ; `session.recovered` porte `"status": "WORKING"` et `recoveredAt`. Les trois ont `containerId`, `numberPhone` et `session`.

bot.needs_review / bot.handoff:

```json
{
  "event": "bot.needs_review",
  "data": {
    "botId": "8b2e…",
    "conversationId": "f41c…",
    "contactPhone": "22212345678",
    "question": "Do you deliver to Rosso?",
    "lang": "fr",
    "timestamp": "2026-09-30T11:02:10.000Z"
  },
  "timestamp": "2026-09-30T11:02:10.180Z"
}
```

`reason` s'ajoute quand ce n'est pas un simple « pas de réponse » : `"away"` (hors horaires ; sans `lang` alors) ou `"ai_quota"` (l'enveloppe d'IA du mois est épuisée).

commerce.order:

```json
{
  "event": "commerce.order",
  "data": {
    "botId": "8b2e…",
    "contactPhone": "22212345678",
    "orderNumber": "A-1042",
    "total": 12500,
    "currency": "MRU",
    "timestamp": "2026-09-30T12:20:44.000Z"
  },
  "timestamp": "2026-09-30T12:20:44.260Z"
}
```

`poll.vote` transmet le vote de WhatsApp tel quel (`vote`, `poll`) avec votre `containerId` et `numberPhone` en plus.

---

## Connect : Claude et autres IA (MCP)

Avec **Connect**, une application d'IA travaille votre numéro WhatsApp : elle lit vos conversations, rédige et envoie des réponses, prospecte, envoie et ouvre des fichiers. Elle parle MCP (Model Context Protocol) en HTTP. Adresse du serveur :

```text
https://app.wahasend.com/api/v1/mcp
```

L'IA réfléchit et écrit (dans son propre compte) ; WahaSend ne fait que transporter les messages et appliquer les [limites d'envoi](https://wahasend.com/docs/fr/connect.md#connect-limits).

<a id="connect-claude"></a>

#### 1. claude.ai, Claude Desktop et l'app Claude (connexion, sans clé)

1. Dans Claude, ouvrez **Réglages → Connecteurs** et touchez **Ajouter un connecteur personnalisé**.
2. Nom : `WahaSend`. Lien : `https://app.wahasend.com/api/v1/mcp`.
3. Touchez **Se connecter**, connectez-vous à WahaSend, choisissez les numéros WhatsApp que Claude peut utiliser (un, plusieurs, ou **Tous mes numéros**, qui inclut ceux ajoutés plus tard) et touchez **Autoriser**.

Derrière, c'est de l'OAuth 2.1 standard (code d'autorisation + PKCE, enregistrement dynamique du client, portée `whatsapp`). Les jetons d'accès durent 60 minutes et se renouvellent pendant 30 jours. La connexion n'atteint que les numéros choisis, et ne marche que sur `/mcp`. Seuls les propriétaires et admins peuvent l'autoriser. La page **Connect** liste les applications connectées avec leurs numéros, permet de changer ces numéros sans se reconnecter (effet à la requête suivante) ou de les déconnecter ; un changement de mot de passe ou un départ de l'organisation les coupe aussi. Un numéro supprimé quitte les applications qui l'avaient ; une application qui n'a plus de numéro est déconnectée.

<a id="connect-key"></a>

#### 2. Claude Code, Cursor, Codex (avec votre clé)

Remplacez `YOUR_KEY` par votre clé de compte (tableau de bord → **Connect**).

Claude Code:

```bash
claude mcp add --transport http wahasend https://app.wahasend.com/api/v1/mcp --header "Authorization: Bearer YOUR_KEY"
```

Claude Desktop ou Cursor (réglages MCP):

```json
{
  "mcpServers": {
    "wahasend": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://app.wahasend.com/api/v1/mcp",
        "--header",
        "Authorization: Bearer YOUR_KEY"
      ]
    }
  }
}
```

Codex : ~/.codex/config.toml:

```toml
[mcp_servers.wahasend]
url = "https://app.wahasend.com/api/v1/mcp"
bearer_token_env_var = "WAHASEND_API_KEY"
```

…puis, dans le terminal où vous lancez Codex:

```bash
export WAHASEND_API_KEY=YOUR_KEY
```

Codex peut aussi se connecter sans clé : `codex mcp add wahasend --url https://app.wahasend.com/api/v1/mcp` ouvre votre navigateur ; `codex mcp login wahasend` reconnecte plus tard. Tout autre client MCP : donnez le lien et envoyez la clé en `Authorization: Bearer` (ou `X-API-Key`).

<a id="connect-chatgpt"></a>

#### 3. ChatGPT

1. Utilisez ChatGPT sur le web avec une offre payante (Plus, Pro, Business, Enterprise ou Education) ; l'app mobile ne peut pas ajouter d'applications.
2. Dans **Réglages**, activez le **mode développeur**.
3. Créez une application nommée `WahaSend` avec le lien `https://app.wahasend.com/api/v1/mcp` et l'authentification **OAuth**.
4. Connectez-vous à WahaSend, choisissez les numéros qu'il peut utiliser et touchez **Autoriser**. Dans une conversation, ouvrez **+** → Mode développeur → WahaSend.

<a id="connect-numbers"></a>

#### Plusieurs numéros WhatsApp

Une connexion peut travailler plusieurs de vos numéros. Lesquels : une connexion Claude atteint les numéros choisis à l'autorisation (ou tous) ; la clé du compte atteint tous les numéros de l'organisation ; une clé de numéro seulement le sien. Aucun outil ne voit ni n'utilise jamais un autre numéro.

- `list_numbers` donne pour chaque numéro son id, son téléphone et son nom (libellé), s'il est connecté, si WhatsApp l'a peut-être bloqué, s'il est le numéro par défaut, et ce qu'il peut encore envoyer aujourd'hui.
- Conversations, réponses et messages disent sur quel numéro ils sont (`number` : id, téléphone, libellé). Un contact qui a écrit à deux numéros a deux conversations. `list_chats`, `read_chat`, `new_replies` et `find_contact` acceptent un `number` facultatif (id, téléphone ou libellé) pour ne regarder qu'un numéro.
- `send_message`, `send_attachment` et `send_to_many` acceptent un `from` facultatif (id, téléphone ou libellé). Sans lui, une réponse part du numéro avec lequel le contact a parlé en dernier, et un premier message de votre numéro par défaut. Quand plusieurs numéros pourraient envoyer et qu'aucun n'est celui par défaut, l'outil répond avec les candidats et l'IA vous demande. Le résultat dit quel numéro a envoyé (`number`) et pourquoi (`numberChosenBy`).
- Les limites restent par numéro : chacun a son espacement, ses plafonds par heure et par jour et sa limite de nouveaux contacts, avec les réglages de votre organisation. Un message n'est jamais déplacé vers un autre numéro parce qu'un numéro est limité, déconnecté ou peut-être bloqué (les [groupes d'envoi](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers) ne s'appliquent pas à Connect). « Nouveau contact » veut dire nouveau pour le numéro qui envoie.
- `check_numbers` et `daily_allowance` acceptent un `number` facultatif ; `daily_allowance` sans lui liste tous les numéros. `open_attachment` récupère le fichier par le numéro de son message.

<a id="connect-tools"></a>

#### Outils

Le serveur donne à l'IA ces outils et des règles (demander avant un premier message, répondre dans la langue du contact, ne jamais contourner une limite…). Les réponses des outils sont en JSON ; un envoi refusé dit pourquoi et jusqu'à quand. Il y a aussi un prompt `prospecting` (arguments `offer`, `language`).

Les descriptions ci-dessous sont celles que lit l'IA, en anglais, avec les limites recommandées.

##### list_numbers

List my WhatsApp numbers. The user's WhatsApp numbers this connection may use: name (label), phone, whether it is connected, whether WhatsApp may have blocked it, whether it is the default, and what it may still send today. Every other tool names numbers the same way (id, phone or label).

Aucune entrée.

##### send_message

Send a WhatsApp message. Send one text message from one of the user's WhatsApp numbers. From: pass `from` (a number's id, phone or label, see list_numbers) to choose which of the user's numbers sends; without it, a reply goes out from the number this contact last talked with, a first message from the default number — when that is unclear the result lists the numbers: ask the user. It never switches to another number by itself. Messages are spaced 30–90 s apart, capped at 60 an hour, 200 a day; the result says when it will go out. A first message to a new contact needs confirmed=true (after the user approved it) and counts against a small daily limit of that number.

- `to` string, obligatoire (1–100 chars): A phone number (+34612345678), a WhatsApp id exactly as returned (…@lid), or a contact name ("Percy")
- `text` string, obligatoire (1–4096 chars)
- `from` string, facultatif (1–100 chars): Which of the user's numbers sends (id, phone or label, see list_numbers). Omit to reply from the number the contact last talked with (or the default number for someone new).
- `confirmed` boolean, facultatif: Required for a first message to a new contact: true only after the user approved this recipient, number and text

##### send_attachment

Send a file on WhatsApp. Send one file (photo, PDF or other document, voice note, video) from one of the user's WhatsApp numbers, with an optional caption. Give exactly one source: forward_from (a message with a file in that number's chats, by the ids read_chat / new_replies return — WhatsApp forwards only within one number), url (a public https link to the file) or content_base64 + filename + mimetype (a file you made or the user gave you). The kind is chosen from the file: JPEG/PNG/WebP → photo, ogg/opus → voice note, MP4 → video, anything else allowed → document. Programs, scripts, web pages and archives are refused. From: pass `from` (a number's id, phone or label, see list_numbers) to choose which of the user's numbers sends; without it, a reply goes out from the number this contact last talked with, a first message from the default number — when that is unclear the result lists the numbers: ask the user. It never switches to another number by itself. Same rules as send_message: Messages are spaced 30–90 s apart, capped at 60 an hour, 200 a day; the result says when it will go out.

- `to` string, obligatoire (1–100 chars): A phone number (+34612345678), a WhatsApp id exactly as returned (…@lid), or a contact name ("Percy")
- `caption` string, facultatif (0–1024 chars): Text shown with the file (not for voice notes)
- `from` string, facultatif (1–100 chars): Which of the user's numbers sends (id, phone or label, see list_numbers). Omit to reply from the number the contact last talked with (or the default number for someone new).
- `confirmed` boolean, facultatif: true only after the user approved this recipient, number, file and caption. Required for a first message to a new contact, and to forward a file someone sent to another person
- `forward_from` object, facultatif
- `forward_from.chat` string, obligatoire (1–100 chars): The chat the file is in: phone, WhatsApp id (…@lid) or contact name
- `forward_from.message_id` uuid, obligatoire: The `id` of the message with the file, as read_chat / new_replies returned it
- `url` string (URL), facultatif (0–2048 chars): A public https link to the file itself
- `content_base64` string, facultatif (4–6991531 chars): The file's bytes, base64 (at most 5 MB)
- `filename` string, facultatif (1–200 chars): With content_base64: the file name, e.g. "price-list.pdf"
- `mimetype` string, facultatif (3–128 chars): With content_base64: its type, e.g. "application/pdf", "text/csv"

##### send_to_many

Send a message to several contacts. Send a text to a list of contacts, all from one of the user's numbers (from; the default number when omitted — never split across numbers). They go out one by one, 30–90 s apart; the result lists when each is scheduled and which could not be sent because a limit of that number was reached. {{name}} in the text is replaced per recipient. Requires confirmed=true, which you may only set after showing the user the recipients, the number and the text and getting their OK.

- `recipients` object[], obligatoire (1–50 items)
- `recipients[].phone` string, obligatoire (7–64 chars): Phone number in international format (+34612345678), or the WhatsApp id exactly as list_chats / new_replies returned it (e.g. 12345678901234@lid). Never strip "@lid".
- `recipients[].name` string, facultatif (0–100 chars)
- `text` string, obligatoire (1–4096 chars): May contain {{name}}
- `from` string, facultatif (1–100 chars): Which of the user's numbers sends (id, phone or label, see list_numbers). Omit to reply from the number the contact last talked with (or the default number for someone new).
- `confirmed` boolean, facultatif: Required: true only after the user approved this exact list, number and text

##### check_numbers

Check which numbers are on WhatsApp. Tell valid WhatsApp numbers from invalid ones before messaging. Does not send anything. Checked through one of the user's numbers (`number`; the default one when omitted).

- `phones` string[], obligatoire (1–500 items)
- `number` string, facultatif (1–100 chars): The user's number to check through (id, phone or label); people who already talked to it need no check

##### find_contact

Find a contact. Look someone up in the contacts of the user's numbers by name or part of their number. Returns their phone (and WhatsApp id when known) and which of the user's numbers know them. Use it before messaging someone the user names.

- `query` string, obligatoire (1–100 chars)
- `limit` integer, facultatif (1–50)
- `number` string, facultatif (1–100 chars): Only the contacts of this one of the user's numbers (id, phone or label)

##### list_chats

List conversations. Conversations of the user's numbers, newest first, with unread counts — one per contact and number (`number` says which of the user's numbers it is on). `phone` is either digits or a WhatsApp id ending in @lid; use it as-is to reply, from the same number.

- `limit` integer, facultatif (1–200)
- `search` string, facultatif (0–100 chars): Filter by phone or name
- `number` string, facultatif (1–100 chars): Only the conversations of this one of the user's numbers (id, phone or label)

##### read_chat

Read a conversation. The exchange with one contact, oldest first: what was sent (with delivery status) and what they replied. Each message has an `id` and the `number` of the user's it is on; messages with a file carry an `attachment` label — pass the id to open_attachment to see it. When the contact talked with several of the user's numbers, pass `number` to read one conversation; never mix them.

- `phone` string, obligatoire (7–64 chars): Phone number in international format (+34612345678), or the WhatsApp id exactly as list_chats / new_replies returned it (e.g. 12345678901234@lid). Never strip "@lid".
- `limit` integer, facultatif (1–500)
- `number` string, facultatif (1–100 chars): The one of the user's numbers whose conversation to read (id, phone or label)

##### open_attachment

Open an attachment. Open the file attached to one WhatsApp message: a photo is shown to you, a PDF or Word (.docx) file comes back as text, a voice note as a transcript. Videos and other files: details only. Use only when the file matters for what the user asked. The file is fetched through the number the message is on.

- `chat` string, obligatoire (1–100 chars): The contact: phone number, WhatsApp id exactly as returned (…@lid), or a contact name — the same chat you read with read_chat
- `message_id` uuid, obligatoire: The `id` of the message, as read_chat / new_replies returned it

##### new_replies

Get new replies. Everything received that has not been read yet, oldest first, each with the `number` of the user's it came in on (reply from that number). By default marks them as read so the next call returns only newer messages.

- `mark_read` boolean, facultatif (default true)
- `limit` integer, facultatif (1–500)
- `number` string, facultatif (1–100 chars): Only what came in on this one of the user's numbers (id, phone or label)

##### delivery_status

Check delivery status. Whether messages were sent, delivered, read or failed.

- `message_ids` uuid[], obligatoire (1–200 items)

##### daily_allowance

Daily allowance. What a number may still send: first messages to new contacts today, messages this hour and today, and when the next message can go out. Limits are per number. Without `number`, every number this connection may use.

- `number` string, facultatif (1–100 chars): One of the user's numbers (id, phone or label)

<a id="connect-limits"></a>

#### Limites d'envoi

Chaque message envoyé par une IA passe par un limiteur par numéro : messages espacés, plafonnés par heure et par jour, et premiers messages aux nouveaux contacts plafonnés selon l'âge du numéro. Ces valeurs par défaut suivent les recommandations publiées (« environ 200 nouveaux clients par jour » de WhatsApp et la pratique des fournisseurs d'API WhatsApp). Elles ont été ajoutées après qu'un outil d'IA a envoyé 35 messages en deux minutes et que le numéro a été bloqué.

| Protection | Valeur recommandée |
| --- | --- |
| Heures d'envoi | 09:00–17:00 (Africa/Nouakchott) |
| Espacement | 30 à 90 s entre deux messages |
| Par heure | 60 messages |
| Par jour | 200 messages |
| Premiers messages aux nouveaux contacts, par jour, selon l'âge du numéro | 20 (jours 0–9), 50 (jours 10–19), 100 (jours 20–29), 200 (dès le jour 30) |
| Démarrage calme | aucun les 3 premiers jours après la liaison |
| Jour de repos | réponses seulement après 3 jours de suite de premiers messages |
| Taux de réponse | pause des premiers messages quand moins de 30 % de ceux des 7 derniers jours ont eu une réponse (à partir de 8 messages) |
| Copier-coller | le même premier message à 3 nouvelles personnes par jour au plus |
| Désinscription | jamais de message à qui a demandé d'arrêter |
| Accord | un premier message à un nouveau contact demande votre accord (`confirmed: true`) |

Le **propriétaire** de l'organisation peut modifier ou désactiver chacune (tableau de bord → **Connect** → Plus de réglages → Limites d'envoi, ou `PUT /mcp/safety` connecté ; `GET /mcp/safety` montre les valeurs actuelles et recommandées). L'IA est informée des vraies valeurs.

> WhatsApp seul décide des numéros qu'il bloque. Les valeurs recommandées réduisent le risque ; aucun outil ne peut le supprimer. Les changer est votre choix et votre responsabilité, et WahaSend n'est pas responsable d'un numéro bloqué.

<a id="connect-files"></a>

#### Fichiers

- **Ouvrir** (`open_attachment`) : une photo est montrée à l'IA (3,75 Mo max), un PDF ou un Word revient en texte (30 000 caractères max), une note vocale en transcription, une vidéo en détails seulement. Fichiers jusqu'à 10 Mo. Le fichier n'est jamais stocké ; chaque ouverture est journalisée sans son contenu. Les notes vocales utilisent l'enveloppe d'IA mensuelle de Connect.
- **Envoyer** (`send_attachment`) : transférer un fichier de vos conversations, un lien **https** public (16 Mo max) ou des octets fournis par l'IA (5 Mo max). Autorisés : photos (JPEG, PNG, WebP), notes vocales (OGG/Opus), vidéos MP4 et 3GP/MOV, PDF, fichiers Office et OpenDocument sans macros, RTF, TXT, CSV, GIF, audio. Programmes, scripts, pages web, archives et fichiers à macros sont refusés ; les premiers octets doivent correspondre au type.
- Un fichier compte comme un message pour toutes les limites. Transférer à quelqu'un d'autre un fichier qu'une personne vous a envoyé demande votre accord explicite.
- WhatsApp garde les fichiers un temps limité : un vieux fichier peut répondre `ATTACHMENT_EXPIRED`.

---

## Erreurs et limites de débit

Chaque erreur répond avec la même enveloppe et un en-tête `X-Request-Id` :

400 VALIDATION_ERROR:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed. Check the details for specific field errors.",
    "details": { "phone": ["Invalid recipient. Use an international phone number (e.g. +22212345678) or a WhatsApp id ending in @lid."] },
    "issues": [{ "field": "phone", "code": "custom", "message": "Invalid recipient. …" }],
    "requestId": "7c9e2f4a-1b3d-4e5f-8a6b-0c1d2e3f4a5b"
  }
}
```

- `code` est stable : testez-le, jamais `message` (le texte peut changer).
- `details` ajoute des faits quand il y en a (`retryAfterSeconds`, `attemptsRemaining`, `products`…). `issues` liste chaque champ incorrect d'un `VALIDATION_ERROR` avec un code comme `required`, `too_big` (avec `maximum`) ou `invalid_uuid`.
- `requestId` (le même que l'en-tête `X-Request-Id`) : donnez-le au support, il nous permet de retrouver votre requête.
- Les réponses 429 et 503 portent `Retry-After` (secondes) quand l'attente est connue. Attendez ce temps avant de réessayer.
- 5xx : on peut réessayer une lecture. Avant de réessayer un envoi, vérifiez `GET /messages` : un délai dépassé a pu partir.

<a id="rate-limits"></a>

#### Limites de débit

| Limite | Valeur | En cas de dépassement |
| --- | --- | --- |
| Modifications par organisation (POST, PUT, PATCH, DELETE ; lectures libres), partagées par toutes les clés et personnes | 300 par minute (pics jusqu'à 300) | 429 `RATE_LIMITED`, `details.scope: "organization"`, `retryAfterSeconds`, `limitPerMinute`, `Retry-After` |
| Envois de texte par numéro | 60 par minute (priorité `high` : sans plafond) | Mis en file, envoyés plus tard ; jamais refusés |
| Messages de campagne | 20 par minute par numéro, 60 par minute par organisation, espacés de 3 à 7 s | Mis en file, envoyés plus tard |
| Codes de vérification ([OTP](https://wahasend.com/docs/fr/errors.md#otp-limits)) | Par numéro : 1 toutes les 30 s, 3 par 10 min, 10 par 24 h ; 60 par minute par organisation | 429 `OTP_RATE_LIMITED`, `Retry-After` |
| Envois par IA via Connect (MCP) | Voir [limites d'envoi](https://wahasend.com/docs/fr/connect.md#connect-limits) | L'outil répond « non envoyé » avec la raison |
| Messages pendant l'[essai gratuit](https://wahasend.com/docs/fr/errors.md#trial-limits) | 100 par jour par organisation (jour UTC), tous les envois comptés | 429 `TRIAL_LIMIT_REACHED`, `details.limit`, `used`, `resetsAt`, `Retry-After` |
| Connexion | 20 essais par 15 min par adresse IP ; 10 mauvais mots de passe bloquent l'e-mail 15 min | 429 `RATE_LIMIT` / `TOO_MANY_LOGIN_ATTEMPTS` |
| Connexion Claude (OAuth) | Enregistrement 60/h par IP ; jeton 120 par 15 min par application ; autorisation et révocation 60 par 15 min | 429 `RATE_LIMIT` |

<a id="otp-limits"></a>

#### Limites OTP

Pour que personne ne puisse se servir de votre formulaire d'inscription pour inonder un numéro de codes (et faire signaler votre numéro), `POST /otp/send` est limité par numéro de destination dans votre organisation : un code toutes les 30 secondes, 3 par 10 minutes et 10 par 24 heures ; et 60 codes par minute pour toute l'organisation. Les fenêtres sont glissantes (pas des minutes ni des jours calendaires). Au-delà, la réponse est 429 `OTP_RATE_LIMITED` avec `Retry-After` et `details` : `scope` (`phone` ou `organization`), `reason` (`cooldown`, `window`, `day` ou `organization`), `limit` et `retryAfterSeconds`. Indiquez à la personne quand elle peut redemander ; ne réessayez pas automatiquement.

<a id="trial-limits"></a>

#### Limites de l'essai gratuit

Un nouveau compte a les produits demandés gratuitement pendant 7 jours (un essai par personne et par produit). Tant que tous les produits de l'organisation sont en essai, elle peut envoyer 100 messages par jour via WahaSend — envois simples, lots (chaque destinataire compte), campagnes (comptées au lancement), rappels, codes OTP, envois de médias, réponses de l'assistant et envois Connect ensemble — et son crédit IA est de 2 $ pour tout l'essai. Le compteur repart à 00:00 UTC. Un envoi qui ne rentre pas est refusé, rien n'est mis en file : 429 `TRIAL_LIMIT_REACHED` avec `Retry-After` (secondes jusqu'à minuit UTC) et `details` : `limit`, `used`, `requested`, `retryAfterSeconds`, `resetsAt`. Un lot ou une campagne plus grand que ce qui reste est refusé en entier. Dès qu'un produit est payé, la limite disparaît. À la fin de l'essai, le produit est en pause (403 `PRODUCT_REQUIRED`) jusqu'au paiement ; rien n'est supprimé.

<a id="error-codes"></a>

#### Tous les codes d'erreur

Généré depuis le catalogue d'erreurs du serveur. Les codes ne sont jamais renommés ni supprimés ; de nouveaux peuvent apparaître.

##### VALIDATION_ERROR

HTTP 400 · Validation failed. Check the details for specific field errors.

##### INVALID_JSON

HTTP 400 · Request body contains invalid JSON.

##### INVALID_ID

HTTP 400 · Invalid identifier.

##### PAYLOAD_TOO_LARGE

HTTP 413 · The request is too large.

##### FILE_TOO_LARGE

HTTP 413 · The file is too large.

##### ROUTE_NOT_FOUND

HTTP 404 · This API route does not exist. Check /api for available endpoints.

##### NOT_FOUND

HTTP 404 · Not found.

##### CONFLICT

HTTP 409 · This resource already exists.

##### REFERENCE_ERROR

HTTP 400 · Referenced resource does not exist.

##### IN_USE

HTTP 409 · This is still used elsewhere and cannot be removed.

##### CONSTRAINT_VIOLATION

HTTP 400 · Operation violates a data constraint.

##### FORBIDDEN_ACTION

HTTP 400 · This action is not allowed.

##### RATE_LIMIT

HTTP 429 · Too many requests. Slow down and try again.

##### RATE_LIMITED

HTTP 429 · Your organization is sending too many requests. Wait a moment and try again.

##### AUTH_ERROR

HTTP 401 · Authentication required.

##### TOO_MANY_LOGIN_ATTEMPTS

HTTP 429 · Too many wrong passwords for this account. Try again in 15 minutes.

##### ACCOUNT_DEACTIVATED

HTTP 403 · Account is deactivated. Contact support.

##### OAUTH_TOKEN_SCOPE

HTTP 403 · This Claude connection can only be used by Claude (MCP endpoint).

##### OAUTH_REQUEST_INVALID

HTTP 400 · This request from Claude has expired or was already answered. Start again from Claude.

##### NUMBER_KEY_SCOPE

HTTP 403 · This API key belongs to one WhatsApp number and can only send and read through it.

##### USER_REQUIRED

HTTP 403 · Sign in to the dashboard to do this; API keys cannot.

##### WRONG_PASSWORD

HTTP 400 · The password is not correct.

##### NO_PASSWORD_SET

HTTP 400 · No password set. Ask the admin to reset your password.

##### INVALID_LINK

HTTP 400 · This link has expired or was already used. Ask for a new one.

##### SIGNUP_DISABLED

HTTP 403 · Sign-up is currently closed.

##### TWO_FACTOR_REQUIRED

HTTP 403 · This organization requires two-step sign-in. Turn it on in your account first.

##### TWO_FACTOR_CODE_INVALID

HTTP 400 · The code is not correct. Check your authenticator app and try again.

##### TWO_FACTOR_CHALLENGE_EXPIRED

HTTP 400 · This sign-in has expired. Enter your email and password again.

##### TWO_FACTOR_SETUP_EXPIRED

HTTP 400 · The set-up has expired. Start again.

##### TWO_FACTOR_ALREADY_ENABLED

HTTP 409 · Two-step sign-in is already on.

##### TWO_FACTOR_NOT_ENABLED

HTTP 409 · Two-step sign-in is not on.

##### TWO_FACTOR_OWNER_FIRST

HTTP 409 · Turn on two-step sign-in for your own account first.

##### TWO_FACTOR_UNAVAILABLE

HTTP 503 · Two-step sign-in is not available on this server yet.

##### COUNTRY_LOCKED

HTTP 409 · Your country can no longer be changed here once you have paid. Write to us to change it.

##### PRODUCT_REQUIRED

HTTP 403 · This feature is not included in your products.

##### NO_ACTIVE_PRODUCT

HTTP 403 · Your account has no active product yet. Contact us to activate one.

##### ADMIN_ONLY

HTTP 403 · This action is restricted to the platform admin.

##### ROLE_FORBIDDEN

HTTP 403 · Your role in this organization does not allow this. Ask an owner or admin.

##### ADMIN_CANNOT_DELETE

HTTP 403 · The platform admin organization cannot be deleted.

##### INVITE_INVALID

HTTP 400 · This invitation has expired, was cancelled or was already used. Ask for a new one.

##### INVITE_EMAIL_MISMATCH

HTTP 403 · This invitation was sent to another email address. Sign in with that address.

##### ALREADY_MEMBER

HTTP 409 · This person is already in your team.

##### TEAM_FULL

HTTP 409 · The team is full.

##### OWNER_ROLE_FIXED

HTTP 409 · The owner stays owner. Transfer the ownership first.

##### CANNOT_REMOVE_OWNER

HTTP 409 · The owner cannot be removed. Transfer the ownership first.

##### OWNER_CANNOT_LEAVE

HTTP 409 · You own this organization. Transfer the ownership before leaving.

##### LAST_ORGANIZATION

HTTP 409 · This is your only organization. Delete your account instead.

##### NOT_OWNER

HTTP 403 · Only the owner can do this.

##### ALREADY_OWNER

HTTP 409 · You already own this organization.

##### OWNS_ORGANIZATION

HTTP 409 · You own an organization. Transfer it to someone else first.

##### USE_LEAVE

HTTP 400 · Use "Leave this organization" to remove yourself.

##### TOO_MANY_ORGANIZATIONS

HTTP 409 · You can own up to 10 organizations.

##### NO_SESSION_CONFIGURED

HTTP 400 · This account has no connected WhatsApp number.

##### SESSION_NOT_CONNECTED

HTTP 409 · WhatsApp is not connected. Reconnect the number.

##### CONTAINER_NOT_FOUND

HTTP 404 · WhatsApp number not found.

##### NUMBER_LIMIT_REACHED

HTTP 403 · This account uses all the WhatsApp numbers it may have (Developer and Connect include 3). Add extra numbers, or remove a number.

##### PORT_EXHAUSTED

HTTP 503 · No room for a new WhatsApp number right now. Contact support.

##### NUMBER_NOT_ON_WHATSAPP

HTTP 400 · This number is not on WhatsApp.

##### NO_VALID_NUMBERS

HTTP 400 · None of these numbers are on WhatsApp.

##### NUMBER_ON_OTHER_ASSISTANT

HTTP 409 · This WhatsApp number already answers for another assistant that is on. Remove it there first, or turn that assistant off.

##### SECRETS_KEY_MISSING

HTTP 503 · SECRETS_ENCRYPTION_KEY must be set to give WhatsApp servers their own keys.

##### NUMBER_VALIDATION_UNAVAILABLE

HTTP 503 · Numbers cannot be checked right now. Try again shortly.

##### OTP_EXPIRED

HTTP 410 · OTP has expired. Request a new code.

##### OTP_ALREADY_VERIFIED

HTTP 400 · OTP has already been verified.

##### OTP_MAX_ATTEMPTS

HTTP 429 · Maximum verification attempts exceeded. Request a new code.

##### OTP_INVALID_CODE

HTTP 400 · Invalid code.

##### OTP_RATE_LIMITED

HTTP 429 · Too many verification codes for this number or this account. Wait before sending another one.

##### OTP_TEMPLATE_MISSING_CODE

HTTP 400 · The message template must contain {{code}}, where the code goes.

##### MESSAGE_NOT_FOUND

HTTP 404 · No message with this id was sent by this account.

##### MESSAGE_NOT_SENT

HTTP 409 · This message has not been sent yet (or it failed). Only sent messages can be edited or deleted.

##### MESSAGE_WRONG_NUMBER

HTTP 403 · This message was sent from another WhatsApp number than the one this request uses.

##### CAMPAIGN_INVALID_STATE

HTTP 400 · The campaign cannot do this in its current state.

##### STAFF_PHONE_LIMIT

HTTP 409 · You can add up to 10 phones for orders.

##### STAFF_PHONE_IS_BUSINESS

HTTP 400 · This is your business WhatsApp number. Add another phone: yours or a staff member's.

##### WEBHOOK_SIGNING_UNAVAILABLE

HTTP 503 · Webhook signing is not available on this server yet (SECRETS_ENCRYPTION_KEY is not set).

##### ATTACHMENT_NOT_FOUND

HTTP 404 · This message was not found in this chat, or it has no attachment.

##### ATTACHMENT_EXPIRED

HTTP 410 · This file is no longer on WhatsApp (older attachments expire). Ask the contact to send it again.

##### ATTACHMENT_TOO_LARGE

HTTP 413 · This file is too large to open here.

##### ATTACHMENT_UNSUPPORTED

HTTP 415 · This kind of file cannot be opened here.

##### TRANSCRIPTION_UNAVAILABLE

HTTP 503 · Voice notes cannot be read because speech-to-text is not enabled on this server.

##### SEND_FILE_TOO_LARGE

HTTP 413 · This file is too large to send on WhatsApp from here.

##### SEND_FILE_TYPE_NOT_ALLOWED

HTTP 415 · This kind of file cannot be sent. Photos, PDFs, Office files, text and CSV files, voice notes, audio and MP4 videos can.

##### SEND_FILE_URL_UNREACHABLE

HTTP 422 · The file at this link cannot be fetched. Use a public https link to the file itself.

##### WAHA_ERROR

HTTP 502 · WhatsApp could not complete the request. Try again.

##### WAHA_SESSION_ERROR

HTTP 503 · WhatsApp is not connected.

##### WAHA_UNREACHABLE

HTTP 503 · WhatsApp service is unreachable. Please try again later.

##### UPSTREAM_TIMEOUT

HTTP 504 · A service we depend on took too long to answer. Try again.

##### AI_UNAVAILABLE

HTTP 503 · The AI service is not available right now. Try again shortly.

##### AI_BUSY

HTTP 503 · The AI service is busy. Try again in a moment.

##### SERVICE_BUSY

HTTP 503 · The service is busy. Try again in a moment.

##### AI_QUOTA_EXCEEDED

HTTP 402 · This month's AI allowance is used up. It renews on the 1st, or contact us to raise it.

##### TRIAL_LIMIT_REACHED

HTTP 429 · Free trial: 100 messages a day. Activate your product to remove this limit.

##### PADDLE_NOT_CONFIGURED

HTTP 503 · Card payment is not available on this server. Use another way to pay.

##### PADDLE_NO_SUBSCRIPTION

HTTP 404 · This organization has no card payment to manage.

##### PADDLE_UNAVAILABLE

HTTP 502 · The card payment service did not answer. Try again in a moment.

##### PADDLE_SIGNATURE_INVALID

HTTP 401 · Invalid or expired Paddle signature.

##### PADDLE_CHANGE_REFUSED

HTTP 409 · This change could not be made. Check your card, or try again later.

##### PADDLE_NOTHING_TO_RESUME

HTTP 409 · This subscription is not set to stop: there is nothing to undo.

##### PADDLE_PERIOD_UNAVAILABLE

HTTP 409 · This billing period is not offered for this product.

##### PADDLE_SAME_PERIOD

HTTP 409 · This product is already billed this way.

##### INVOICE_NOT_AVAILABLE

HTTP 404 · No receipt to download for this payment.

##### INTERNAL_ERROR

HTTP 500 · An unexpected error occurred. Please try again or contact support.

---

## Limites et bonnes pratiques

WahaSend relie votre numéro comme WhatsApp Web, en appareil connecté. WhatsApp observe le comportement des numéros et bloque ceux qui ressemblent à des machines ou à du spam. Nous espaçons et cadençons les messages pour réduire le risque, mais **aucun outil ne peut garantir qu'un numéro ne sera jamais bloqué** : c'est votre usage qui compte le plus.

- Écrivez à des gens qui vous connaissent et attendent votre message : clients, personnes qui ont écrit en premier, personnes qui ont accepté.
- Commencez doucement avec un nouveau numéro : quelques premiers messages par jour, davantage après quelques semaines. Gardez aussi un usage humain normal du téléphone.
- Personnalisez : un nom et une raison dans chaque message. Le même texte à des centaines d'inconnus est le chemin le plus court vers un blocage.
- Vérifiez d'abord les numéros ([vérifier](https://wahasend.com/docs/fr/api/validate.md)) ; beaucoup de messages vers des numéros absents de WhatsApp ressemblent à du spam.
- Envoyez en journée, et arrêtez quand les gens ne répondent plus ou vous le demandent.
- Gardez la priorité `high` pour l'urgent (codes, alertes) : elle saute la simulation de frappe.
- Surveillez les événements `session.disconnected` et `GET /session/status` : un numéro bloqué ou délié n'envoie plus.

WahaSend est un service indépendant. Il n'est ni conçu, ni approuvé, ni soutenu par WhatsApp.

---

## Nouveautés

<a id="changelog-2026-10-15"></a>

#### 15 octobre 2026

- **Numéros WhatsApp avec Developer et Connect.** Un compte avec Developer ou Connect actif (payé ou en essai gratuit) a 3 numéros WhatsApp inclus ; chaque numéro supplémentaire coûte 10 $ par mois (un an coûte 10 mois), par carte ou par virement depuis la page Mon abonnement. Notify, Assistant et Shop seuls ajoutent toujours autant de numéros que nécessaire. Les numéros que vous avez déjà ne sont jamais supprimés : au-delà de l'allocation, vous envoyez toujours depuis tous et ne pouvez pas en ajouter. `GET /session/list` renvoie `allowance`, et un numéro de trop répond 403 `NUMBER_LIMIT_REACHED` avec `details` ; voir [Numéros WhatsApp](https://wahasend.com/docs/fr/api/numbers.md).

<a id="changelog-2026-10-06"></a>

#### 6 octobre 2026

- **Nouveaux prix.** Developer se prend maintenant seul à 39 $ par mois (390 $ par an), avec un essai gratuit, par carte ou virement. Shop passe à 119 $ et Connect à 35 $ par mois ; Notify (29 $) et Assistant (79 $) ne changent pas. Un an coûte 10 mois.
- **Prix locaux.** En Mauritanie, en Afrique de l'Ouest et au Maghreb, des prix plus bas dans la monnaie locale (MRU, FCFA, MAD, DZD, TND), payés par virement ou mobile money. Les paiements par carte restent en dollars US au prix international. L'API est la même partout.

<a id="changelog-2026-10-01"></a>

#### 1er octobre 2026

- **Plusieurs numéros.** Les envois qui ne nomment pas de numéro peuvent passer par un groupe de vos numéros (secours ou partage, même contact même numéro) : voir [Plusieurs numéros](https://wahasend.com/docs/fr/phone-numbers.md#several-numbers). `/alert/send`, `/alert/batch` et les envois de médias renvoient `containerId`. Chaque produit permet d'ajouter autant de numéros WhatsApp que nécessaire.
- **Webhooks signés.** Chaque événement porte `X-WahaSend-Signature`, `X-WahaSend-Event` et `X-WahaSend-Delivery` ; voir [comment vérifier](https://wahasend.com/docs/fr/webhooks.md#webhook-security). Nouveaux `GET /me/webhook` et `POST /me/webhook/regenerate-secret`. Les événements de statut, reçus, numéros et sondages portent maintenant `containerId` et `numberPhone`. Les propriétaires d'Assistant et de Shop peuvent définir un webhook pour leurs événements.
- **Clés d'API pour chaque produit.** Notify, Assistant et Shop peuvent créer une clé de compte et appeler leurs propres routes (p. ex. campagnes et rappels pour Notify). Voir [quels produits utilisent l'API](https://wahasend.com/docs/fr/authentication.md#auth-products).
- **Limites OTP.** Par numéro et par organisation, 429 `OTP_RATE_LIMITED` au-delà ([détails](https://wahasend.com/docs/fr/errors.md#otp-limits)). Un modèle sans `{{code}}` est refusé (`OTP_TEMPLATE_MISSING_CODE`) ; chaque `{{code}}` est remplacé.
- **Modifier et supprimer par `messageId`.** `/alert/edit`, `/alert/delete` et `/alert/forward` acceptent l'identifiant WahaSend (les identifiants WhatsApp marchent toujours). Les envois de médias sont enregistrés dans `GET /messages` avec des événements de statut ; le `messageId` de leur réponse est maintenant celui de WahaSend et celui de WhatsApp est dans `wahaMessageId`. Les lignes de `GET /messages` montrent `wahaMessageId` et `containerId`.
- **Campagnes.** `stats.delivered` et `stats.read` comptent les accusés WhatsApp ; nouvelle [annulation](https://wahasend.com/docs/fr/api/campaigns.md#campaign-cancel) ; modification d'une campagne `scheduled` (y compris `scheduledAt`) ; suppression de tout ce qui n'est pas `sending` ; `scheduledAt` accepte tout décalage ISO ; `creditsRequired` a disparu.
- **Petites corrections.** `/alert/batch` respecte `skipInvalidNumbers: false` ; `/alert/forward` n'exige plus `fromPhone` ; une clé de numéro ne marque lus que les messages de son numéro ; `GET /api` renvoie ici.
- **Organisations et équipes.** Un compte est une organisation avec des membres (propriétaire, admins, agents). Les clés d'API agissent en admin de l'organisation ; le rôle est vérifié à chaque requête.
- **Connexion Claude.** claude.ai, les apps Claude, ChatGPT et Codex se connectent à Connect par OAuth : plus de clé à copier.
- **Fichiers dans Connect.** `open_attachment` lit photos, PDF, fichiers Word et notes vocales ; `send_attachment` envoie des fichiers.
- **Erreurs et limites.** Une seule enveloppe d'erreur avec des codes stables et `requestId` ; `Retry-After` sur 429/503 ; une limite de 300 modifications par minute par organisation.
- Cette documentation a été réécrite depuis le code, avec un test qui échoue quand elle s'en écarte.
