OmniManager Docs

Ressources

Envoyer des messages, statuts et groupes

Envoyer/lister les messages WhatsApp, interroger l'état de livraison, publier un statut, lister les groupes, et gérer les contacts consentants.

Messages

MéthodeCheminPermissionObjet
POST/shops/{idOrName}/messagesmessages.sendmettre en file un message WhatsApp → 202
GET/shops/{idOrName}/messages?status=&limit=&cursor=messages.readliste, les plus récents d'abord
GET/messages/{id}messages.readétat de livraison d'un message
curl -s -X POST -H "Authorization: Bearer omk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel":"whatsapp","to":"+24177000000","text":"Hello!",
       "mediaUrl":"https://example.com/photo.jpg","mediaType":"image",
       "idempotencyKey":"order-1234-confirmation"}' \
  https://app.omni-manager.com/api/v1/shops/SHOP_ID/messages

Tous les champs sauf channel et to sont optionnels ; text ou mediaUrl est requis (sinon 422 EMPTY_MESSAGE). to doit être au format E.164 (+CCxxxxxxxxx). Au plus un mediaUrl par appel — le contrat de cet endpoint est toujours une seule pièce jointe. Répéter un appel avec le même idempotencyKey renvoie le message d'origine (200, en-tête X-Idempotent-Replay: true) et n'envoie jamais deux fois.

Un appel réussi renvoie 202 avec le message mis en file et le verdict de la politique qui l'a laissé passer :

{ "id": "…", "status": "queued", "statusUrl": "/api/v1/messages/…",
  "policy": {
    "recipientClass": "known",     // "known" | "cold" | "declared" | "group"
    "warmupDay": 12,
    "caps": { "perDay": 100, "perHour": 16, "coldPerDay": 10, "declaredPerDay": 50 },
    "usage": { "today": 3, "lastHour": 1, "coldToday": 0, "declaredToday": 0 }
  } }

Interrogez GET /messages/{id} pour l'état final : sent, failed (erreur de livraison — BOT_RESTARTED, WHATSAPP_SESSION_LOST, BOT_SERVICE_UNREACHABLE, ou WHATSAPP_NOT_CONNECTED une fois qu'un message a trop longtemps attendu une session qui n'est jamais revenue), ou rejected (RECIPIENT_NOT_ON_WHATSAPP) — un message ne reste jamais queued indéfiniment.

Statuts

MéthodeCheminPermissionObjet
POST/shops/{idOrName}/statusesstatuses.postpublier un statut WhatsApp via le numéro de la boutique → 202

Corps : { caption?, mediaUrl?, mediaType? } ("image" ou "video" ; caption ou mediaUrl requis, caption plafonné à ~700 caractères, une seule pièce jointe). Soumis à son propre plafond quotidien, statusesPerDay, distinct des plafonds de messages ci-dessus — 429 STATUSES_DAILY_CAP_REACHED une fois épuisé.

Groupes

MéthodeCheminPermissionObjet
GET/shops/{idOrName}/groups?canSend=&minParticipants=&q=groups.readles groupes WhatsApp de la boutique

Chaque élément est { id, name, participantCount, isAdmin, announce, canSend }. canSend vaut exactement isAdmin — pas !announce || isAdmin — parce que le désabonnement ne peut pas fonctionner par membre à l'intérieur d'un groupe et la simple appartenance n'est pas une preuve de consentement à la diffusion ; administrer le groupe est le seul signal disponible, il est donc requis pour envoyer à N'IMPORTE QUEL groupe, pas seulement ceux en mode « annonces ». Filtrez avec ?canSend=true, ?minParticipants=50, ?q=sous-chaîne du nom.

Envoyer à un groupe se fait avec le même POST …/messages ci-dessus, avec un JID de groupe comme to ; recipientClass dans le verdict de la politique vaut alors "group", et les plafonds spécifiques aux groupes de la politique anti-bannissement s'appliquent à la place des plafonds 1:1.

Contacts consentants

Un tenant peut déclarer qu'un numéro n'est pas un inconnu, le faisant passer du compartiment cold au compartiment declared (son propre budget quotidien, généralement plus large — voir Messagerie et politique anti-bannissement).

MéthodeCheminPermissionObjet
GET/shops/{idOrName}/contacts/consented?activeOnly=&limit=&cursor=contacts.readlister les contacts déclarés (non isolés par boutique en stockage — chaque boutique du tenant voit la même liste)
POST/shops/{idOrName}/contacts/consentedcontacts.writedéclarer un ou plusieurs numéros
DELETE/shops/{idOrName}/contacts/consentedcontacts.writerévoquer un numéro

POST est toujours un lot, même d'un seul élément : { "contacts": [{ "phone": "+241…", "note"?, "expiresAt"? }] }. Plafonné à une taille de lot et à un plafond de déclarations réellement nouvelles par jour (les deux côté serveur, non pilotés par le tenant) ; dépasser l'un ou l'autre renvoie 429 DECLARE_BATCH_TOO_LARGE / 429 DECLARE_DAILY_CAP_REACHED sans toucher à celles qui seraient passées. Une déclaration faite via cette route enregistre toujours source: "api". DELETE prend { "phone": "+241…" } et révoque (revokedAt renseigné), sans jamais supprimer définitivement — la piste d'audit reste.

Un numéro qui répond STOP / ARRÊT / DÉSABONNER / UNSUBSCRIBE se désabonne, qu'il ait été déclaré ou non — l'envoi suivant vers lui échoue avec 422 RECIPIENT_OPTED_OUT tant qu'il n'écrit pas à nouveau.

MarkdownSchéma OpenAPI