Référence API

API Partenaire

Une API REST pour tout piloter par programmation : clients, connexions, templates, campagnes, messages et abonnement. Construisez votre propre interface par-dessus.

Authentification#

Toutes les requêtes utilisent une clé API côté serveur. Générez-en une dans votre tableau de bord sous Clés API — elle ressemble à pk_live_… et n'est affichée qu'une seule fois. Envoyez-la comme bearer token :

En-tête
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxx
!

Gardez les clés côté serveur

Une clé peut envoyer des messages au nom des clients qu'elle couvre. Ne l'exposez jamais dans un navigateur ou une application mobile — appelez l'API uniquement depuis votre backend.

Portée de la clé#

Une clé est soit globale (tous vos clients), soit rattachée à un seul client. Choisissez le client au moment de générer la clé dans Clés API. Une clé client ne résout que ce client : les listes ne renvoient que ses lignes, un id appartenant à un autre client répond 404, et GET /api/v1/subscription répond 403 — vous pouvez donc la remettre à ce client (ou l'utiliser pour son intégration) sans exposer vos autres clients. Les clés globales fonctionnent exactement comme avant.

Conventions#

URL de base https://wha-platform.genuka.com/api/v1. Les corps et les réponses sont en JSON. Les endpoints de liste renvoient { "data": [...] }. Tout est automatiquement limité à votre compte partenaire. Les erreurs utilisent les codes de statut HTTP avec { "error": "code", "message": "…" }.

Entreprises (clients)#

GET/api/v1/companiesLister les clients onboardés avec leurs connexions
GET/api/v1/companies/{id}Un client unique + connexions + décomptes
GET /api/v1/companies
curl https://wha-platform.genuka.com/api/v1/companies -H "Authorization: Bearer pk_live_xxx"

{
  "data": [
    {
      "id": "cmp_123",
      "name": "Acme Coffee",
      "onboardedAt": "2026-06-01T10:12:00.000Z",
      "connections": [
        { "id": "con_1", "wabaId": "1029…", "phoneNumberId": "1065…",
          "displayPhoneNumber": "+237 6 90 …", "qualityRating": "GREEN", "status": "connected" }
      ],
      "_count": { "templates": 4 }
    }
  ]
}

Connexions (numéros)#

GET/api/v1/connectionsLister les connexions WhatsApp (optionnel ?companyId=)

Une connexion est un WABA + numéro de téléphone. Son id est ce que vous transmettez lors de la création de templates, de campagnes ou de l'envoi de messages.

Templates#

Les templates sont des mises en page de message pré-approuvées. Il vous en faut un pour démarrer une conversation (c.-à-d. écrire à un client en dehors de la fenêtre de 24 heures — voir Messages). Vous créez un template ici, Meta l'examine, et l'approbation / le rejet arrive automatiquement sur GET /templates/{id} (et sur vos webhooks).

GET/api/v1/templatesLister les templates (optionnel ?companyId=)
POST/api/v1/templatesCréer et soumettre un template à Meta
GET/api/v1/templates/{id}Un template + son historique de statuts
DELETE/api/v1/templates/{id}Supprimer sur Meta et localement

Comment fonctionne la création#

Vous transmettez le tableau components de Meta tel quel. Cela garde cet endpoint léger tout en vous laissant construire n'importe quel template pris en charge par Meta — texte, en-têtes média, boutons, OTP, etc. Les seuls champs requis sont connectionId, name et components. category est l'un de MARKETING, UTILITY ou AUTHENTICATION (par défaut MARKETING); language est une locale Meta telle que en_US ou fr (par défaut en).

Référence des composants#

Un template est une liste ordonnée de composants. Chacun a un type :

HEADERformat: TEXT | IMAGE | VIDEO | DOCUMENT | LOCATIONOptionnel. Un seul par template. Les en-têtes média nécessitent un handle d'exemple.
BODYtext + exampleRequis (sauf AUTHENTICATION). Contient les variables {{1}}… ou {{name}}.
FOOTERtextPied de page court optionnel. Pas de variables.
BUTTONSQUICK_REPLY | URL | PHONE_NUMBER | COPY_CODE | OTPOptionnel. Jusqu'à 10 boutons (les règles varient selon le type).
i

Les exemples sont obligatoires

Tout composant avec des variables (ou un en-tête média) doit inclure un example afin que Meta puisse l'examiner : "example": { "body_text": [["Alice", "#1024"]] } pour le corps, "example": { "header_handle": ["<id>"] } pour un en-tête média.

Template utilitaire / marketing (en-tête + corps + boutons)#

POST /api/v1/templates
curl -X POST https://wha-platform.genuka.com/api/v1/templates \
  -H "Authorization: Bearer pk_live_xxx" -H "Content-Type: application/json" \
  -d '{
    "connectionId": "con_1",
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "components": [
      { "type": "HEADER", "format": "IMAGE",
        "example": { "header_handle": ["4::aW1hZ2Uv..."] } },
      { "type": "BODY",
        "text": "Hi {{1}}, order {{2}} just shipped. Track it any time.",
        "example": { "body_text": [["Alice", "#1024"]] } },
      { "type": "FOOTER", "text": "Reply STOP to opt out" },
      { "type": "BUTTONS", "buttons": [
        { "type": "URL", "text": "Track order",
          "url": "https://acme.co/track/{{1}}", "example": ["https://acme.co/track/1024"] },
        { "type": "QUICK_REPLY", "text": "Need help" }
      ] }
    ]
  }'

{ "data": { "id": "tpl_123", "status": "pending", "providerId": "12534…" } }

L'en-tête média example.header_handle est le handle d'upload reprenable renvoyé par l'upload média de Meta — au moment de l'envoi vous fournissez l'image réelle par URL ou identifiant de média (voir Messages → template).

Paramètres nommés#

Vous préférez {{name}} au positionnel {{1}} ? Définissez parameterFormat: "NAMED" et donnez à chaque variable un parameter_name dans l'exemple.

POST /api/v1/templates (named)
{
  "connectionId": "con_1",
  "name": "appointment_reminder",
  "language": "en_US",
  "category": "UTILITY",
  "parameterFormat": "NAMED",
  "components": [
    { "type": "BODY",
      "text": "Hi {{customer_name}}, your appointment is on {{date}}.",
      "example": { "body_text_named_params": [
        { "param_name": "customer_name", "example": "Alice" },
        { "param_name": "date", "example": "June 20" }
      ] } }
  ]
}

Template d'authentification (OTP)#

Les templates d'authentification délivrent des codes à usage unique. Le corps et le texte du bouton sont fixés par WhatsApp — vous ne rédigez pas le contenu. Vous choisissez seulement le type de bouton et quelques options. Aucun en-tête, média, URL ou emoji n'est autorisé.

COPY_CODEotp_type: COPY_CODELe client touche pour copier le code. Le plus simple, fonctionne partout.
ONE_TAPotp_type: ONE_TAPSaisie automatique Android. Nécessite package_name + signature_hash.
ZERO_TAPotp_type: ZERO_TAPCode délivré silencieusement à l'application. Nécessite la même liaison d'application.
POST /api/v1/templates (authentication)
{
  "connectionId": "con_1",
  "name": "verification_code",
  "language": "en_US",
  "category": "AUTHENTICATION",
  "messageSendTtlSeconds": 600,
  "components": [
    { "type": "BODY", "add_security_recommendation": true },
    { "type": "FOOTER", "code_expiration_minutes": 10 },
    { "type": "BUTTONS", "buttons": [
      { "type": "OTP", "otp_type": "COPY_CODE", "text": "Copy code" }
    ] }
  ]
}

Pour ONE_TAP / ZERO_TAP, ajoutez la liaison d'application au bouton OTP : "autofill_text": "Autofill", "supported_apps": [{ "package_name": "com.acme.app", "signature_hash": "K8a..." }].

i

Envoyer le code

La création du template n'envoie jamais rien. Pour délivrer un code, vous envoyez un message template avec le raccourci otp — voir Messages → template d'authentification.

Campagnes#

GET/api/v1/campaignsLister les campagnes (optionnel ?companyId=)
POST/api/v1/campaignsCréer une campagne avec des destinataires
GET/api/v1/campaigns/{id}Une campagne avec ses statistiques de livraison
GET/api/v1/campaigns/{id}/recipientsStatut par destinataire (?status=, ?limit=)
POST/api/v1/campaigns/{id}/launchEnvoyer à tous les destinataires en attente

Créer une campagne#

Chaque destinataire porte ses propres variables. La forme simple est un tableau positionnel pour les paramètres du corps {{1}}, {{2}}…. Pour les en-têtes média, les boutons ou les codes OTP, transmettez plutôt un objet riche — { "body": [...], "header": {...}, "buttons": [...] } — la même structure acceptée par Messages → template. Le template doit être approved avant le lancement.

POST /api/v1/campaigns
curl -X POST https://wha-platform.genuka.com/api/v1/campaigns \
  -H "Authorization: Bearer pk_live_xxx" -H "Content-Type: application/json" \
  -d '{
    "connectionId": "con_1",
    "templateId": "tpl_123",
    "name": "June promo",
    "recipients": [
      { "to": "+237690000001", "variables": ["Alice", "#1024"] },
      { "to": "+237690000002", "variables": ["Bob", "#1025"] }
    ]
  }'

{ "data": { "id": "cmp_9", "name": "June promo", "status": "draft", "_count": { "recipients": 2 } } }

Lancer#

POST /api/v1/campaigns/{id}/launch
curl -X POST https://wha-platform.genuka.com/api/v1/campaigns/cmp_9/launch \
  -H "Authorization: Bearer pk_live_xxx"

{ "data": { "sent": 2, "failed": 0, "skipped": 0 } }
i

Limites

La plateforme ne facture ni ne comptabilise les messages — Meta facture directement le WABA de chaque client. Si la limite de campagnes de votre abonnement est atteinte en cours d'envoi, les destinataires restants sont skipped (relancez à la période suivante ou passez à un forfait supérieur). La V1 envoie de façon synchrone — gardez des listes modestes ; l'envoi en file d'attente pour les grandes listes est prévu sur la feuille de route.

Messages#

POST/api/v1/messagesEnvoyer un message unique de n'importe quel type

Un seul endpoint envoie tous les types de message WhatsApp. Transmettez toujours connectionId et to, puis exactement un champ de contenu du tableau ci-dessous. Ajoutez replyTo (le wamidd'un message reçu) pour citer/répondre à un message.

TEMPLATEbillableTemplate pré-approuvé. Le seul moyen de démarrer une conversation en dehors de la fenêtre de 24 h.
TEXTfree*{ "text": "Hi" } or { "text": { "body": "…", "previewUrl": true } }
IMAGE / VIDEO / AUDIO / DOCUMENT / STICKERfree*{ "image": { "link": "…" } } or { "id": "<media-id>" }; caption/filename optional
LOCATIONfree*{ "location": { "latitude": …, "longitude": …, "name": "…", "address": "…" } }
CONTACTSfree*{ "contacts": [ … Meta contact objects … ] }
REACTIONfree*{ "reaction": { "messageId": "wamid…", "emoji": "👍" } }
BUTTONS / LIST / CTAfree*Boutons de réponse interactifs, un menu liste, ou un bouton URL d'appel à l'action.
LOCATIONREQUESTfree*{ "locationRequest": { "body": "Where should we deliver?" } } — affiche un bouton Envoyer la position.
RAWfree*Échappatoire pour tout le reste (Flows, adresse, catalogue, appel vocal) : un fragment de type Cloud API complet.
i

La fenêtre de 24 heures

Seuls les envois template peuvent démarrer une conversation. Tout ce qui est marqué free* est un message de session en format libre : il n'est délivré que si le client a écrit à l'entreprise au cours des dernières 24 heures. En dehors de cette fenêtre, utilisez un template. Tous les envois renvoient { "data": { "messageId": "wamid…" } }.

Un 200 signifie que Meta a accepté le message, et non qu'il a été livré. Le statut final (sentdeliveredread, ou failed) arrive de façon asynchrone sur vos webhooks. Pour to, incluez toujours le + et l'indicatif pays (par ex. +237690000001) — l'omettre peut mal router le message. Les médias transmis par link sont mis en cache par Meta pendant ~10 minutes, donc réutilisez la même URL pour le même asset (ou ajoutez une chaîne de requête unique pour vider le cache).

Message template#

Le cas simple est uniquement les variables du corps. variables est un tableau positionnel ; vous pouvez aussi utiliser bodyNamed pour les templates nommés, header pour un en-tête média/texte, et buttons pour des paramètres de bouton dynamiques.

POST /api/v1/messages (template, simple)
curl -X POST https://wha-platform.genuka.com/api/v1/messages \
  -H "Authorization: Bearer pk_live_xxx" -H "Content-Type: application/json" \
  -d '{
    "connectionId": "con_1",
    "to": "+237690000001",
    "template": { "name": "order_shipped", "language": "en_US", "variables": ["Alice", "#1024"] }
  }'

{ "data": { "messageId": "wamid.HBg…" } }
POST /api/v1/messages (template, header + body + button)
{
  "connectionId": "con_1",
  "to": "+237690000001",
  "template": {
    "name": "order_shipped",
    "language": "en_US",
    "header": { "image": { "link": "https://acme.co/orders/1024.png" } },
    "variables": ["Alice", "#1024"],
    "buttons": [
      { "type": "url", "text": "1024" }
    ]
  }
}

Le buttons[].text remplit la partie dynamique d'un bouton URL (le {{1}} dans https://acme.co/track/{{1}}). Pour une réponse rapide, utilisez { "type": "quick_reply", "payload": "…" }; pour un code de coupon, utilisez { "type": "copy_code", "code": "SAVE20" }. Paramètres de corps nommés : "bodyNamed": { "customer_name": "Alice" }.

Template d'authentification#

Transmettez le code une seule fois via le raccourci otp — nous remplissons à la fois le corps et le bouton OTP pour vous (le format requis par Meta).

POST /api/v1/messages (authentication)
{
  "connectionId": "con_1",
  "to": "+237690000001",
  "template": { "name": "verification_code", "language": "en_US", "otp": "472913" }
}

Messages de session en format libre#

text
{ "connectionId": "con_1", "to": "+237690000001", "text": "Thanks, talk soon!" }
image (with caption)
{ "connectionId": "con_1", "to": "+237690000001",
  "image": { "link": "https://acme.co/promo.jpg", "caption": "New arrivals 🎉" } }
document
{ "connectionId": "con_1", "to": "+237690000001",
  "document": { "link": "https://acme.co/invoice.pdf", "filename": "invoice-1024.pdf" } }
interactive reply buttons
{
  "connectionId": "con_1",
  "to": "+237690000001",
  "buttons": {
    "body": "Confirm your order?",
    "footer": "Acme Coffee",
    "buttons": [
      { "id": "yes", "title": "Confirm" },
      { "id": "no",  "title": "Cancel" }
    ]
  }
}
interactive list
{
  "connectionId": "con_1",
  "to": "+237690000001",
  "list": {
    "body": "Pick a delivery slot",
    "button": "Choose",
    "sections": [
      { "title": "Today", "rows": [
        { "id": "t1", "title": "12:00–14:00" },
        { "id": "t2", "title": "14:00–16:00", "description": "Most popular" }
      ] }
    ]
  }
}
call-to-action URL button
{
  "connectionId": "con_1",
  "to": "+237690000001",
  "cta": { "body": "Your receipt is ready.", "displayText": "View receipt", "url": "https://acme.co/r/1024" }
}
reply in-thread + reaction
{ "connectionId": "con_1", "to": "+237690000001", "replyTo": "wamid.HBg…", "text": "On its way!" }

{ "connectionId": "con_1", "to": "+237690000001",
  "reaction": { "messageId": "wamid.HBg…", "emoji": "👍" } }

Abonnement#

GET/api/v1/subscriptionPlan, limites et usage actuels
GET /api/v1/subscription
{
  "plan": { "code": "pro", "name": "Pro" },
  "interval": "monthly",
  "currency": "XAF",
  "status": "active",
  "currentPeriodEnd": "2026-07-18T00:00:00.000Z",
  "usage": { "clients": 7, "numbers": 9, "campaigns": 142 },
  "limits": { "maxClients": 20, "maxNumbers": 20, "monthlyCampaigns": 2000 }
}

Erreurs#

Exemples
401 { "error": "missing_bearer_token" }      // no Authorization header
401 { "error": "invalid_token" }             // unknown / revoked key, or inactive partner
403 { "error": "account_deactivated", "message": "Deactivated Account" }
                                              // the client is suspended: its own key stops
                                              // working, and any request naming it is refused
                                              // — including with a partner-wide key
400 { "error": "missing_fields", "message": "…" }
400 { "error": "missing_content", "message": "…" }  // no content field on a message send
400 { "error": "invalid_media", "message": "…" }    // media without an id or link
402 { "error": "plan_limit_clients" }         // subscription client limit exceeded
402 { "error": "plan_limit_numbers" }         // subscription number limit exceeded
402 { "error": "plan_limit_campaigns" }       // subscription campaign limit exceeded
404 { "error": "connection_not_found" }
404 { "error": "template_not_found" }
409 { "error": "template_exists" }
502 { "error": "send_failed", "message": "…" }     // Meta rejected the send
502 { "error": "meta_rejected", "message": "…" }   // Meta rejected the template