Documentation API

SDK officiels — Node.js et Python

Installer et utiliser @reachflow/sdk (npm) et reachflow (PyPI) pour l'API REST v1.

12 min de lecture · Mis à jour le 6 juin 2026

Objectif

Intégrer ReachFlow depuis votre backend Node.js ou Python sans appeler l'API REST à la main.

ReachFlow propose deux bibliothèques client open source qui encapsulent l'API publique v1 : authentification, gestion des erreurs, retry et helpers de polling.

LangagePackageRegistryVersion minimale
Node.js / TypeScript@reachflow/sdknpmNode.js ≥ 18
PythonreachflowPyPIPython ≥ 3.10

[!note] Les SDK couvrent les endpoints intégrateur (/api/v1). La gestion des clés API et des webhooks reste dans le dashboard ReachFlow.


Prérequis

Avant d'installer un SDK :

  1. Clé API — créez une clé rfl_live_… ou rfl_test_… dans API → Clés API (guide)
  2. Plan ou add-on — Starter+ ou add-on Accès API sur le plan Gratuit (détail)
  3. Provider connecté — au moins un numéro WhatsApp au statut connected (GET /providers)
  4. Scopes — la clé doit inclure les scopes nécessaires (messages:send, otp:send, etc.)

Stockez la clé dans une variable d'environnement côté serveur — jamais dans le frontend ni dans un dépôt Git.

export REACHFLOW_API_KEY="rfl_live_…"

Installation

Node.js / TypeScript

npm install @reachflow/sdk

Python

pip install reachflow

Configuration du client

URL de base

EnvironnementbaseUrl / base_url
Sandboxhttps://sandbox-api.reachflow.me (défaut SDK)
Productionhttps://api.reachflow.me (ou votre domaine API)

Le SDK ajoute automatiquement le préfixe /api/v1.

Node.js

import { ReachFlow } from '@reachflow/sdk';

const client = new ReachFlow({
  apiKey: process.env.REACHFLOW_API_KEY!,
  baseUrl: 'https://sandbox-api.reachflow.me', // optionnel
  timeoutMs: 30_000,   // optionnel — défaut 30 s
  maxRetries: 2,       // optionnel — retry 429 / 5xx
});

Python

import os
from reachflow import ReachFlow

client = ReachFlow(
    api_key=os.environ["REACHFLOW_API_KEY"],
    base_url="https://sandbox-api.reachflow.me",  # optionnel
    timeout_ms=30_000,
    max_retries=2,
)

# Fermeture propre de la connexion HTTP
with ReachFlow(api_key=os.environ["REACHFLOW_API_KEY"]) as client:
    ...

Python asynchrone

from reachflow import AsyncReachFlow

async with AsyncReachFlow(api_key="rfl_live_…") as client:
    providers = await client.providers.list()

Démarrage rapide (30 min)

1. Lister vos numéros WhatsApp

Node.js

const { providers } = await client.providers.list();

for (const p of providers) {
  console.log(p.id, p.name, p.status, p.phoneNumber);
}

// Helper : premier numéro connecté
const connected = await client.providers.findConnected();

Python

data = client.providers.list()
for p in data["providers"]:
    print(p["id"], p["name"], p["status"], p.get("phoneNumber"))

connected = client.providers.find_connected()

2. Envoyer un message texte

Node.js

const { messageId } = await client.messages.send({
  providerId: connected!.id,
  to: '22996123456',
  message: 'Votre commande #12345 est confirmée.',
});

// Attendre un statut terminal (sent, delivered, failed, cancelled)
const status = await client.messages.waitForTerminal(messageId);
console.log(status.status);

Python

result = client.messages.send(
    provider_id=connected["id"],
    to="22996123456",
    message="Votre commande #12345 est confirmée.",
)

status = client.messages.wait_for_terminal(result["messageId"])
print(status["status"])

La réponse initiale est toujours queued (HTTP 202). Le message part ensuite en arrière-plan avec frappe simulée (indicateur « en train d'écrire »).

3. Consulter le statut

Node.js

const status = await client.messages.getStatus(messageId);

Python

status = client.messages.get_status(message_id)
StatutSignification
queuedAccepté, en attente d'envoi
processingEnvoi en cours
sentExpédié vers WhatsApp
deliveredLivré (accusé de réception)
failedÉchec — voir failureCode
cancelledAnnulé

Messages

Texte

Équivalent de POST /api/v1/messages/send.

// Node
await client.messages.send({
  providerId: 'uuid',
  to: '22996123456',
  message: 'Bonjour {{prenom}}',
  variables: { prenom: 'Awa' },
  scheduleAt: '2026-12-01T09:00:00.000Z', // optionnel
  saveContact: false,                        // défaut : pas de création contact
  idempotencyKey: 'order-12345',             // optionnel
});
# Python
client.messages.send(
    provider_id="uuid",
    to="22996123456",
    message="Bonjour {{prenom}}",
    variables={"prenom": "Awa"},
    schedule_at="2026-12-01T09:00:00.000Z",
    save_contact=False,
    idempotency_key="order-12345",
)

Média (URL HTTPS publique)

Équivalent de POST /api/v1/messages/send-media.

await client.messages.sendMedia({
  providerId: 'uuid',
  to: '22996123456',
  mediaUrl: 'https://example.com/facture.pdf',
  mediaType: 'document',
  caption: 'Votre facture',
});
client.messages.send_media(
    provider_id="uuid",
    to="22996123456",
    media_url="https://example.com/facture.pdf",
    media_type="document",
    caption="Votre facture",
)

Types acceptés : image, document, audio, video.

Envoi en lot

Équivalent de POST /api/v1/messages/send-bulk (petit volume, même modèle).

const bulk = await client.messages.sendBulk({
  providerId: 'uuid',
  messageTemplate: 'Bonjour {{nom}}, votre RDV est confirmé.',
  recipients: [
    { to: '22996123456', variables: { nom: 'Awa' } },
    { to: '22997123456', variables: { nom: 'Kofi' } },
  ],
});
console.log(bulk.accepted, bulk.rejected, bulk.messageIds);
bulk = client.messages.send_bulk(
    provider_id="uuid",
    message_template="Bonjour {{nom}}, votre RDV est confirmé.",
    recipients=[
        {"to": "22996123456", "variables": {"nom": "Awa"}},
        {"to": "22997123456", "variables": {"nom": "Kofi"}},
    ],
)
print(bulk["accepted"], bulk["rejected"], bulk["messageIds"])

OTP

Flux en deux étapes : envoi du code par WhatsApp, puis vérification côté serveur.

Attention

Le code OTP n'est jamais retourné par l'API ni les SDK. Il arrive uniquement sur WhatsApp du destinataire.

Envoyer un OTP

// Node
const { otpId, messageId } = await client.otp.send({
  providerId: 'uuid',
  phoneNumber: '22996123456',
  brandName: 'Mon App',       // optionnel — défaut : nom du tenant
  codeLength: 6,              // optionnel
  expiresIn: 300,             // secondes, optionnel
});
# Python
otp = client.otp.send(
    provider_id="uuid",
    phone_number="22996123456",
    brand_name="Mon App",
    code_length=6,
    expires_in=300,
)
otp_id = otp["otpId"]

Vérifier le code saisi par l'utilisateur

const result = await client.otp.verify({ otpId, code: '482910' });
if (result.valid) {
  // authentification OK
} else {
  console.log(result.reason, result.attemptsLeft);
}
result = client.otp.verify(otp_id=otp_id, code="482910")
if result["valid"]:
    ...
else:
    print(result.get("reason"), result.get("attemptsLeft"))

Raisons possibles si valid: false : invalid_code, expired, max_attempts_reached, already_used, etc.


Providers

Méthode NodeMéthode PythonEndpoint REST
providers.list()providers.list()GET /providers
providers.get(id)providers.get(id)GET /providers/{id}
providers.findConnected()providers.find_connected()(helper SDK)

Le champ API s'appelle providerId ; en base ReachFlow l'entité est channel_providers.


Gestion des erreurs

Les deux SDK lèvent une exception typée :

Node.jsPython
ReachFlowErrorReachFlowError
import { ReachFlow, ReachFlowError } from '@reachflow/sdk';

try {
  await client.messages.send({ /* … */ });
} catch (err) {
  if (err instanceof ReachFlowError) {
    console.error(err.statusCode, err.code, err.message);
    if (err.retryable) {
      // retry manuel ou laissez maxRetries gérer les cas automatiques
    }
  }
}
from reachflow import ReachFlow, ReachFlowError

try:
    client.messages.send(...)
except ReachFlowError as err:
    print(err.status_code, err.code, err.message)
    if err.retryable:
        ...

Codes d'erreur courants

codeHTTPAction recommandée
unauthorized401Vérifier la clé API
plan_required403Upgrader le plan ou activer l'add-on API
insufficient_scope403Ajouter le scope à la clé (dashboard)
rate_limit_exceeded429Réessayer après délai (retryAfterMs / backoff)
validation_error400Corriger le corps de la requête
not_found404Vérifier providerId ou messageId

Codes d'échec message (failureCode)

Lorsque status === 'failed' :

CodeSignification
warmup_daily_limitPlafond montée en puissance atteint
risk_circuit_openNuméro suspendu (score de risque critique)
provider_disconnectedInstance WhatsApp déconnectée
provider_bannedNuméro banni
instance_busyInstance occupée — réessayez
send_errorErreur technique à l'envoi

Consultez Politique d'envoi API et le dashboard Instances pour la montée en puissance.


Bonnes pratiques

Sécurité

  • Clé API uniquement côté serveur (backend, worker, cron)
  • Une clé par environnement (dev / staging / prod)
  • Scopes minimaux (messages:send sans otp:send si OTP inutile)
  • Révoquez les clés compromises depuis le dashboard

Idempotence

Pour éviter les doublons (paiement, webhook e-commerce) :

await client.messages.send({
  providerId, to, message,
  idempotencyKey: `order-${orderId}`,
});
client.messages.send(
    provider_id=provider_id,
    to=to,
    message=message,
    idempotency_key=f"order-{order_id}",
)

Polling vs webhooks

  • waitForTerminal / wait_for_terminal — pratique pour scripts et tests
  • Webhooks sortants — recommandé en production (guide webhooks) pour recevoir message.sent, message.delivered, message.failed

Quota

Les envois API consomment le même quota messages que les campagnes dashboard (Token Bucket du plan).


Exemple complet — confirmation de commande

Node.js (Express)

import express from 'express';
import { ReachFlow, ReachFlowError } from '@reachflow/sdk';

const app = express();
app.use(express.json());

const reachflow = new ReachFlow({
  apiKey: process.env.REACHFLOW_API_KEY!,
  baseUrl: process.env.REACHFLOW_API_URL,
});

app.post('/orders/:id/notify', async (req, res) => {
  const { providerId, phone, customerName } = req.body;

  try {
    const { messageId } = await reachflow.messages.send({
      providerId,
      to: phone,
      message: `Merci ${customerName}, commande #${req.params.id} confirmée.`,
      idempotencyKey: `order-notify-${req.params.id}`,
    });

    res.json({ ok: true, messageId });
  } catch (err) {
    if (err instanceof ReachFlowError) {
      return res.status(err.statusCode || 500).json({
        error: err.code,
        message: err.message,
      });
    }
    throw err;
  }
});

app.listen(3000);

Python (FastAPI)

import os
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from reachflow import ReachFlow, ReachFlowError

app = FastAPI()

class NotifyBody(BaseModel):
    provider_id: str
    phone: str
    customer_name: str

@app.post("/orders/{order_id}/notify")
def notify_order(order_id: str, body: NotifyBody):
    with ReachFlow(api_key=os.environ["REACHFLOW_API_KEY"]) as client:
        try:
            result = client.messages.send(
                provider_id=body.provider_id,
                to=body.phone,
                message=f"Merci {body.customer_name}, commande #{order_id} confirmée.",
                idempotency_key=f"order-notify-{order_id}",
            )
            return {"ok": True, "messageId": result["messageId"]}
        except ReachFlowError as err:
            raise HTTPException(
                status_code=err.status_code or 500,
                detail={"error": err.code, "message": err.message},
            )

Ressources

RessourceLien
Package npmnpmjs.com/package/@reachflow/sdk
Package PyPIpypi.org/project/reachflow
OpenAPI v1/api/v1/openapi.json sur votre instance
Référence REST Messages/developpeurs/reference/messages
Référence REST OTP/developpeurs/reference/otp
Créer une clé API/developpeurs/guides/cles-api

Changelog SDK

Les SDK suivent un versionnement semver indépendant de ReachFlow :

SDKVersion initialeAPI cible
@reachflow/sdk0.1.0REST v1
reachflow0.1.0REST v1

Pour les mises à jour :

npm update @reachflow/sdk
pip install -U reachflow

Cet article vous a-t-il aidé ?

Sommaire de l'article