Référence API

Tous les endpoints de JulakAI : codes OTP, SMS et campagnes.

Base URL
https://api.otp.julakai.comhttp://localhost:3100 (dev local)

Authentification

Endpoints /v1/otp/*, /v1/sms/* et /v1/campaigns : signature HMAC-SHA256. Endpoints /v1/dashboard/* : session cookie.

Message signé : method + url + timestamp + rawBody
url inclut la query string pour les GET.
POST/v1/otp/send
Envoyer un OTP
Body
{ "phone": "+22375673336" }
Réponse
{ "requestId": "otp_...", "expiresInSeconds": 300 }
400 format · 402 quota · 429 cooldown · 503 échec
POST/v1/otp/verify
Vérifier un OTP
Body
{ "phone": "+22375673336", "code": "1234" }
Réponse
{ "verified": true, "verificationToken": "eyJhbG..." }
401 expiré/invalide · 403 verrouillé
GET/v1/otp/status?phone=+22375673336
État d'un OTP
Réponse
{ "state": "pending", "expiresInSeconds": 187 }
POST/v1/sms/send
Envoyer un SMS
1 crédit par SMS de 160 caractères (70 avec des caractères spéciaux), 6 SMS au plus. La référence, facultative, empêche un double envoi.
Body
{ "to": "+22375673336", "text": "Votre commande est prête.", "reference": "cmd-42" }
Réponse
{ "id": "...", "to": "+22375673336", "status": "sent", "segments": 1, "messageId": "..." }
status : sent · failed · blocked (STOP) · duplicate (référence déjà envoyée) · refused (crédits, pays, longueur)
POST/v1/sms/send-batch
Envoyer un lot (100 messages au plus)
Body
{ "messages": [ { "to": "+22375673336", "text": "Bonjour Awa" }, { "to": "+22376000000", "text": "Bonjour Fatim" } ] }
Réponse
{ "results": [ { "to": "+22375673336", "status": "sent", "segments": 1 }, ... ], "sent": 2, "segments": 2 }
Chaque message est indépendant : l'échec de l'un n'empêche pas les autres.
POST/v1/campaigns
Lancer une campagne
10 000 destinataires au plus. Un numéro sans indicatif prend le pays par défaut de l'app (le premier ouvert), ou celui de son champ « country » (« SN », « Sénégal », « +221 ») ; s'il peut être de plusieurs pays, il revient dans rapport.ambigus. Débit, horaires d'envoi (heure UTC), crédits et STOP sont gérés par le service ; une reprise n'envoie jamais deux fois.
Body
{ "name": "Arrivage", "template": "Bonjour {prenom}, ...", "recipients": [ { "to": "75673336", "variables": { "prenom": "Awa" } } ], "windowStart": 8, "windowEnd": 20, "start": true }
Réponse
{ "id": "...", "status": "sending", "counts": { "total": 1, "pending": 1, ... }, "credits": { "needed": 1, "enough": true }, "rapport": { "invalides": [], "doublons": 0 } }
Suivi : GET /v1/campaigns/:id et /:id/recipients · pilotage : POST /:id/pause, /resume, /cancel
POST/v1/sms/opt-outs
Désinscrire un numéro (STOP)
Body
{ "phone": "+22375673336" }
Réponse
{ "phone": "+22375673336", "scope": "", "optedOut": true }
DELETE pour réinscrire · GET pour la liste. Les codes OTP partent toujours.
POST/v1/apps
Créer une app
Header X-Admin-Key requis. appSecret affiché une seule fois.
Body
{ "name": "Mon App", "senderName": "MonApp", "ownerEmail": "moi@exemple.com" }
Réponse
{ "appId": "...", "appKey": "jak_live_...", "appSecret": "sk_live_..." }
PATCH/v1/apps/:id/plan
Changer le plan
Header X-Admin-Key requis.
Body
{ "planStatus": "active" }
Réponse
{ "appId": "...", "planStatus": "active" }