Documentation
L'API Mapi
Envoyez des SMS et des codes OTP vers tous les opérateurs de Madagascar avec une seule intégration : envoi unitaire ou groupé, vérification de codes, historique et solde.
Sommaire
Démarrage
Présentation
Pour échanger par SMS avec ses clients à Madagascar, une entreprise doit d'ordinaire signer un contrat avec chaque opérateur : autant d'intégrations différentes à développer, tester et maintenir, et autant de factures à régler.
Mapi se charge de la connexion aux opérateurs malgaches. L'API choisit automatiquement l'opérateur qui correspond au numéro du destinataire et, si l'un d'eux rencontre un problème, bascule vers un autre pour que le SMS parte quand même. URL de base :
https://messaging.mapi.mg/api- Toutes les requêtes passent en HTTPS.
- Les requêtes
POSTenvoient leurs champs en formulaire (multipart/form-data). Les noms de champs commencent par une majuscule :Recipient,Message… - Les réponses sont en JSON et contiennent toujours un booléen
status. - Les exemples utilisent des numéros au format local à 10 chiffres (
0340000000).
Démarrage
Démarrage rapide
1. Créer un compte
Les appels à l'API se font avec les identifiants d'un compte client Mapi. Commencez par créer votre compte, puis rechargez-le en unités SMS.
2. Obtenir un jeton
curl -X POST 'https://messaging.mapi.mg/api/authentication/login' \
-F 'Username=votre_identifiant' \
-F 'Password=votre_mot_de_passe'La réponse contient un jeton valable 15 minutes :
{
"status": true,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYXBpIn0.bvxPol52…"
}3. Envoyer un premier SMS
Transmettez le jeton tel quel dans l'en-tête Authorization :
curl -X POST 'https://messaging.mapi.mg/api/msg/send' \
-H 'Authorization: VOTRE_JETON' \
-F 'Recipient=0340000000' \
-F 'Message=Votre commande est prête.' \
-F 'Channel=sms'Démarrage
Authentification
POST/authentication/login
Échangez les identifiants de votre compte contre un jeton. Ce jeton a une durée de vie de 15 minutes (900 secondes) et doit être renouvelé régulièrement.
| Champ | Type | Requis | Description |
|---|---|---|---|
Username | chaîne | Requis | Identifiant de votre compte Mapi. |
Password | chaîne | Requis | Mot de passe du compte. |
curl -X POST 'https://messaging.mapi.mg/api/authentication/login' \
-F 'Username=votre_identifiant' \
-F 'Password=votre_mot_de_passe'const body = new FormData();
body.append("Username", "votre_identifiant");
body.append("Password", "votre_mot_de_passe");
const response = await fetch("https://messaging.mapi.mg/api/authentication/login", {
method: "POST",
body,
});
console.log(await response.json());import requests
response = requests.post(
"https://messaging.mapi.mg/api/authentication/login",
data={
"Username": "votre_identifiant",
"Password": "votre_mot_de_passe",
},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/authentication/login');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'Username' => 'votre_identifiant',
'Password' => 'votre_mot_de_passe',
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("Username", "votre_identifiant")
.addFormDataPart("Password", "votre_mot_de_passe")
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/authentication/login")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/authentication/login");
request.Content = new MultipartFormDataContent
{
{ new StringContent("votre_identifiant"), "Username" },
{ new StringContent("votre_mot_de_passe"), "Password" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("Username", "votre_identifiant")
writer.WriteField("Password", "votre_mot_de_passe")
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/authentication/login", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJtYXBpIn0.bvxPol52…"
}{
"status": false,
"code": 1,
"message": "Check login or password"
}Utiliser le jeton
Chaque endpoint protégé attend le jeton dans l'en-tête Authorization, tel quel et sans préfixe Bearer :
Authorization: VOTRE_JETON401 Unauthorized avec le message Authentication credentials required. Reconnectez-vous alors pour obtenir un nouveau jeton, puis rejouez la requête.SMS
Envoyer un SMS
POST/msg/send
Jeton requisEnvoie un message à un destinataire. L'opérateur est déduit du numéro.
| Champ | Type | Requis | Description |
|---|---|---|---|
Recipient | chaîne | Requis | Numéro du destinataire, par exemple 0340000000. |
Message | chaîne | Requis | Texte du SMS. |
Channel | chaîne | Requis | Canal d'envoi : toujours sms. |
curl -X POST 'https://messaging.mapi.mg/api/msg/send' \
-H 'Authorization: VOTRE_JETON' \
-F 'Recipient=0340000000' \
-F 'Message=Votre commande est prête.' \
-F 'Channel=sms'const body = new FormData();
body.append("Recipient", "0340000000");
body.append("Message", "Votre commande est prête.");
body.append("Channel", "sms");
const response = await fetch("https://messaging.mapi.mg/api/msg/send", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
body,
});
console.log(await response.json());import requests
response = requests.post(
"https://messaging.mapi.mg/api/msg/send",
headers={"Authorization": "VOTRE_JETON"},
data={
"Recipient": "0340000000",
"Message": "Votre commande est prête.",
"Channel": "sms",
},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/msg/send');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
CURLOPT_POSTFIELDS => [
'Recipient' => '0340000000',
'Message' => 'Votre commande est prête.',
'Channel' => 'sms',
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("Recipient", "0340000000")
.addFormDataPart("Message", "Votre commande est prête.")
.addFormDataPart("Channel", "sms")
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/msg/send")
.addHeader("Authorization", "VOTRE_JETON")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/msg/send");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
request.Content = new MultipartFormDataContent
{
{ new StringContent("0340000000"), "Recipient" },
{ new StringContent("Votre commande est prête."), "Message" },
{ new StringContent("sms"), "Channel" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("Recipient", "0340000000")
writer.WriteField("Message", "Votre commande est prête.")
writer.WriteField("Channel", "sms")
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/msg/send", body)
req.Header.Set("Authorization", "VOTRE_JETON")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"dryRun": false,
"result": "Sent message 'Votre commande est prête.' to 0340000000"
}{
"status": false,
"dryRun": false,
"result": "Receiver number: Le numéro de téléphone est malformé"
}{
"status": false,
"message": "Authentication credentials required"
}dryRun vaut false pour un envoi réel. Un numéro mal formé renvoie 409 Conflict.
SMS
Envoi groupé personnalisé
POST/msg/sendBulkSms
Jeton requisEnvoie un message différent à chaque destinataire, à partir d'un fichier Excel ou CSV (.xls, .xlsx ou .csv). Le fichier contient une ligne par destinataire :
| Colonne | Contenu | Requis |
|---|---|---|
1 | Nom | Optionnel |
2 | Prénom | Optionnel |
3 | Numéro de téléphone | Requis |
4 | Remarque | Optionnel |
Dernière | Message personnalisé | Requis |
| A | B | C | D | E | |
|---|---|---|---|---|---|
| 1 | Rakoto | Jean | 0340000000 | Client VIP | Bonjour Jean, votre commande n°1042 est prête. |
| 2 | Rasoa | Hery | 0320000000 | Bonjour Hery, votre rendez-vous est confirmé pour demain. |
| Champ | Type | Requis | Description |
|---|---|---|---|
File | fichier | Requis | Fichier .xls, .xlsx ou .csv des destinataires. |
curl -X POST 'https://messaging.mapi.mg/api/msg/sendBulkSms' \
-H 'Authorization: VOTRE_JETON' \
-F 'File=@destinataires.xlsx'import { openAsBlob } from "node:fs";
const body = new FormData();
body.append("File", await openAsBlob("destinataires.xlsx"), "destinataires.xlsx");
const response = await fetch("https://messaging.mapi.mg/api/msg/sendBulkSms", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
body,
});
console.log(await response.json());import requests
with open("destinataires.xlsx", "rb") as file:
response = requests.post(
"https://messaging.mapi.mg/api/msg/sendBulkSms",
headers={"Authorization": "VOTRE_JETON"},
files={"File": file},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/msg/sendBulkSms');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
CURLOPT_POSTFIELDS => [
'File' => new CURLFile('destinataires.xlsx'),
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("File", "destinataires.xlsx",
RequestBody.create(new File("destinataires.xlsx"), MediaType.parse("application/octet-stream")))
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/msg/sendBulkSms")
.addHeader("Authorization", "VOTRE_JETON")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/msg/sendBulkSms");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
request.Content = new MultipartFormDataContent
{
{ new StreamContent(File.OpenRead("destinataires.xlsx")), "File", "destinataires.xlsx" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, err := os.Open("destinataires.xlsx")
if err != nil {
panic(err)
}
defer file.Close()
part, _ := writer.CreateFormFile("File", "destinataires.xlsx")
io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/msg/sendBulkSms", body)
req.Header.Set("Authorization", "VOTRE_JETON")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"dryRun": false,
"result": "12 messages sent."
}{
"status": false,
"message": "File is missing, check the arguments and retry."
}{
"status": false,
"result": "Echec du téléversement.Le type de fichier que vous tentez d'envoyer n'est pas autorisé."
}{
"status": false,
"dryRun": false,
"result": "Plus aucun SMS disponible. Consultez la section OFFRE pour recharger votre compte."
}{
"status": false,
"message": "Authentication credentials required"
}409 Conflict. Consultez vos unités disponibles avant un envoi important.SMS
Même message à plusieurs destinataires
POST/msg/sendUniqueSmsToRecipients
Jeton requisEnvoie le même texte à une liste de destinataires regroupés dans un fichier .xls, .xlsx ou .csv. Placez les numéros dans la première colonne du fichier.
| Champ | Type | Requis | Description |
|---|---|---|---|
File | fichier | Requis | Fichier des destinataires, un numéro par ligne en colonne A. |
Message | chaîne | Requis | Texte envoyé à tous les destinataires. |
curl -X POST 'https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients' \
-H 'Authorization: VOTRE_JETON' \
-F 'File=@destinataires.xlsx' \
-F 'Message=Nos bureaux seront fermés lundi.'import { openAsBlob } from "node:fs";
const body = new FormData();
body.append("File", await openAsBlob("destinataires.xlsx"), "destinataires.xlsx");
body.append("Message", "Nos bureaux seront fermés lundi.");
const response = await fetch("https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
body,
});
console.log(await response.json());import requests
with open("destinataires.xlsx", "rb") as file:
response = requests.post(
"https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients",
headers={"Authorization": "VOTRE_JETON"},
data={
"Message": "Nos bureaux seront fermés lundi.",
},
files={"File": file},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
CURLOPT_POSTFIELDS => [
'File' => new CURLFile('destinataires.xlsx'),
'Message' => 'Nos bureaux seront fermés lundi.',
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("File", "destinataires.xlsx",
RequestBody.create(new File("destinataires.xlsx"), MediaType.parse("application/octet-stream")))
.addFormDataPart("Message", "Nos bureaux seront fermés lundi.")
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients")
.addHeader("Authorization", "VOTRE_JETON")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
request.Content = new MultipartFormDataContent
{
{ new StreamContent(File.OpenRead("destinataires.xlsx")), "File", "destinataires.xlsx" },
{ new StringContent("Nos bureaux seront fermés lundi."), "Message" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, err := os.Open("destinataires.xlsx")
if err != nil {
panic(err)
}
defer file.Close()
part, _ := writer.CreateFormFile("File", "destinataires.xlsx")
io.Copy(part, file)
writer.WriteField("Message", "Nos bureaux seront fermés lundi.")
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/msg/sendUniqueSmsToRecipients", body)
req.Header.Set("Authorization", "VOTRE_JETON")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"dryRun": false,
"result": "12 messages sent."
}{
"status": false,
"message": "Message is missing"
}{
"status": false,
"message": "File is missing, check the arguments and retry."
}{
"status": false,
"result": "Echec du téléversement.Le type de fichier que vous tentez d'envoyer n'est pas autorisé."
}{
"status": false,
"dryRun": false,
"result": "Plus aucun SMS disponible. Consultez la section OFFRE pour recharger votre compte."
}{
"status": false,
"message": "Authentication credentials required"
}SMS
Historique des SMS
GET/msg
Jeton requisListe les messages envoyés depuis votre compte, page par page.
| Paramètre | Type | Description |
|---|---|---|
limit | entier | Nombre de messages renvoyés, par exemple 10. |
offset | entier | Position du premier message renvoyé (0 pour la première page). |
curl 'https://messaging.mapi.mg/api/msg?limit=10&offset=0' \
-H 'Authorization: VOTRE_JETON'const response = await fetch("https://messaging.mapi.mg/api/msg?limit=10&offset=0", {
headers: { Authorization: "VOTRE_JETON" },
});
console.log(await response.json());import requests
response = requests.get(
"https://messaging.mapi.mg/api/msg",
headers={"Authorization": "VOTRE_JETON"},
params={"limit": 10, "offset": 0},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/msg?limit=10&offset=0');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/msg?limit=10&offset=0")
.addHeader("Authorization", "VOTRE_JETON")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://messaging.mapi.mg/api/msg?limit=10&offset=0");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://messaging.mapi.mg/api/msg?limit=10&offset=0", nil)
req.Header.Set("Authorization", "VOTRE_JETON")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"result": [
{
"IdMessage": "1",
"IdCampaign": null,
"Name": null,
"Telephone": "+261340000000",
"Message": "Votre commande est prête.",
"SendStatus": "SENT",
"GeneratedAt": "2024-04-04 11:12:26",
"NumberOfSms": "1"
},
{
"IdMessage": "2",
"IdCampaign": null,
"Name": null,
"Telephone": "+261320000000",
"Message": "Ceci est un test",
"SendStatus": "NOT_SENT",
"GeneratedAt": "2023-12-05 09:06:30",
"NumberOfSms": "1"
}
],
"totalCount": 720
}{
"status": false,
"message": "Authentication credentials required"
}totalCount donne le nombre total de messages, pour construire la pagination. Chaque élément de result contient :
| Champ | Type | Description |
|---|---|---|
IdMessage | chaîne | Identifiant du message. |
IdCampaign | chaîne | null | Campagne à laquelle appartient le message, null sinon. |
Name | chaîne | null | Nom du destinataire, s'il est connu. |
Telephone | chaîne | Numéro du destinataire au format international (+261…). |
Message | chaîne | Texte envoyé. |
SendStatus | chaîne | SENT (envoyé) ou NOT_SENT (non envoyé). |
GeneratedAt | chaîne | Date d'envoi, au format AAAA-MM-JJ HH:MM:SS. |
NumberOfSms | chaîne | Nombre de SMS consommés par le message. |
OTP
Demander un OTP
POST/otp/request
Jeton requisGénère un code à usage unique et l'envoie par SMS au destinataire. Le code est valable 15 minutes.
| Champ | Type | Requis | Description |
|---|---|---|---|
Recipient | chaîne | Requis | Numéro qui reçoit le code. |
Channel | chaîne | Requis | Canal d'envoi : toujours sms. |
curl -X POST 'https://messaging.mapi.mg/api/otp/request' \
-H 'Authorization: VOTRE_JETON' \
-F 'Recipient=0340000000' \
-F 'Channel=sms'const body = new FormData();
body.append("Recipient", "0340000000");
body.append("Channel", "sms");
const response = await fetch("https://messaging.mapi.mg/api/otp/request", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
body,
});
console.log(await response.json());import requests
response = requests.post(
"https://messaging.mapi.mg/api/otp/request",
headers={"Authorization": "VOTRE_JETON"},
data={
"Recipient": "0340000000",
"Channel": "sms",
},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/otp/request');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
CURLOPT_POSTFIELDS => [
'Recipient' => '0340000000',
'Channel' => 'sms',
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("Recipient", "0340000000")
.addFormDataPart("Channel", "sms")
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/otp/request")
.addHeader("Authorization", "VOTRE_JETON")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/otp/request");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
request.Content = new MultipartFormDataContent
{
{ new StringContent("0340000000"), "Recipient" },
{ new StringContent("sms"), "Channel" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("Recipient", "0340000000")
writer.WriteField("Channel", "sms")
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/otp/request", body)
req.Header.Set("Authorization", "VOTRE_JETON")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"dryRun": false,
"code": "000000",
"message": {
"success": "Le message pour +261340000000 a été soumis avec succès auprès de l'opérateur.",
"SubmissionId": "xxxxxxxxxxxxxxxxxxxx"
}
}{
"status": false,
"message": "Authentication credentials required"
}code). Ne le transmettez jamais à votre front-end : faites saisir le code reçu par SMS à l'utilisateur, puis vérifiez-le côté serveur avec /otp/verify.OTP
Vérifier un OTP
POST/otp/verify
Jeton requisVérifie le code saisi par l'utilisateur. Un code ne peut être validé qu'une seule fois.
| Champ | Type | Requis | Description |
|---|---|---|---|
Recipient | chaîne | Requis | Numéro auquel le code a été envoyé. |
Code | chaîne | Requis | Code saisi par l'utilisateur. |
curl -X POST 'https://messaging.mapi.mg/api/otp/verify' \
-H 'Authorization: VOTRE_JETON' \
-F 'Recipient=0340000000' \
-F 'Code=123456'const body = new FormData();
body.append("Recipient", "0340000000");
body.append("Code", "123456");
const response = await fetch("https://messaging.mapi.mg/api/otp/verify", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
body,
});
console.log(await response.json());import requests
response = requests.post(
"https://messaging.mapi.mg/api/otp/verify",
headers={"Authorization": "VOTRE_JETON"},
data={
"Recipient": "0340000000",
"Code": "123456",
},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/otp/verify');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
CURLOPT_POSTFIELDS => [
'Recipient' => '0340000000',
'Code' => '123456',
],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("Recipient", "0340000000")
.addFormDataPart("Code", "123456")
.build();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/otp/verify")
.addHeader("Authorization", "VOTRE_JETON")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/otp/verify");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
request.Content = new MultipartFormDataContent
{
{ new StringContent("0340000000"), "Recipient" },
{ new StringContent("123456"), "Code" },
};
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("Recipient", "0340000000")
writer.WriteField("Code", "123456")
writer.Close()
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/otp/verify", body)
req.Header.Set("Authorization", "VOTRE_JETON")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"message": "Code is verified"
}{
"status": false,
"code": 5,
"message": "Code already used"
}{
"status": false,
"code": 4,
"message": "Code not found"
}{
"status": false,
"message": "Authentication credentials required"
}En cas de refus, le champ code de la réponse précise la raison : 4 pour un code introuvable, 5 pour un code déjà utilisé.
OTP
Historique des OTP
GET/otp
Jeton requisListe les codes générés, avec leur état d'envoi, d'expiration et d'utilisation.
| Paramètre | Type | Description |
|---|---|---|
limit | entier | Nombre de codes renvoyés, par exemple 10. |
offset | entier | Position du premier code renvoyé (0 pour la première page). |
curl 'https://messaging.mapi.mg/api/otp?limit=10&offset=0' \
-H 'Authorization: VOTRE_JETON'const response = await fetch("https://messaging.mapi.mg/api/otp?limit=10&offset=0", {
headers: { Authorization: "VOTRE_JETON" },
});
console.log(await response.json());import requests
response = requests.get(
"https://messaging.mapi.mg/api/otp",
headers={"Authorization": "VOTRE_JETON"},
params={"limit": 10, "offset": 0},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/otp?limit=10&offset=0');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/otp?limit=10&offset=0")
.addHeader("Authorization", "VOTRE_JETON")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://messaging.mapi.mg/api/otp?limit=10&offset=0");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://messaging.mapi.mg/api/otp?limit=10&offset=0", nil)
req.Header.Set("Authorization", "VOTRE_JETON")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"result": [
{
"IdOneTimePassword": "1",
"IdClient": "1",
"Client": "client",
"Username": "username",
"Telephone": "+261340000000",
"Code": "000000",
"GeneratedAt": "1725440581",
"GeneratedAtDateTime": "2024-09-04 11:03:01",
"ExpiresAt": "1725441481",
"ExpiresAtDateTime": "2024-09-04 11:18:01",
"Duration": "900",
"TimeToLiveHHMMSS": "-00:01:25",
"TimeToLiveSeconds": "-85",
"IsExpired": "1",
"SendStatus": "SENT",
"IsAlreadyUsed": "1"
}
],
"totalCount": 3
}{
"status": false,
"message": "Authentication credentials required"
}Champs principaux de chaque élément de result :
| Champ | Type | Description |
|---|---|---|
Telephone | chaîne | Numéro du destinataire au format international. |
Code | chaîne | Code envoyé. |
GeneratedAtDateTime | chaîne | Date de génération (GeneratedAt : horodatage Unix). |
ExpiresAtDateTime | chaîne | Date d'expiration (ExpiresAt : horodatage Unix). |
Duration | chaîne | Durée de validité en secondes (900). |
TimeToLiveSeconds | chaîne | Temps restant avant expiration, négatif une fois le code expiré (TimeToLiveHHMMSS au format HH:MM:SS). |
IsExpired | chaîne | 1 si le code a expiré, 0 sinon. |
IsAlreadyUsed | chaîne | 1 si le code a déjà été validé, 0 sinon. |
SendStatus | chaîne | SENT ou NOT_SENT. |
Compte
Unités disponibles
GET/smsoffer/available
Jeton requisRenvoie le nombre d'unités SMS que votre compte peut encore utiliser.
curl 'https://messaging.mapi.mg/api/smsoffer/available' \
-H 'Authorization: VOTRE_JETON'const response = await fetch("https://messaging.mapi.mg/api/smsoffer/available", {
headers: { Authorization: "VOTRE_JETON" },
});
console.log(await response.json());import requests
response = requests.get(
"https://messaging.mapi.mg/api/smsoffer/available",
headers={"Authorization": "VOTRE_JETON"},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/smsoffer/available');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/smsoffer/available")
.addHeader("Authorization", "VOTRE_JETON")
.get()
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://messaging.mapi.mg/api/smsoffer/available");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://messaging.mapi.mg/api/smsoffer/available", nil)
req.Header.Set("Authorization", "VOTRE_JETON")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"available_sms": 1000
}{
"status": false,
"message": "Authentication credentials required"
}available_sms ; il correspond aux unités de vos packs SMS. Quand il atteint zéro, les envois renvoient 409 Conflict.Compte
Déconnexion
POST/authentication/logout
Jeton requisRévoque le jeton transmis dans l'en-tête Authorization avant son expiration.
curl -X POST 'https://messaging.mapi.mg/api/authentication/logout' \
-H 'Authorization: VOTRE_JETON'const response = await fetch("https://messaging.mapi.mg/api/authentication/logout", {
method: "POST",
headers: { Authorization: "VOTRE_JETON" },
});
console.log(await response.json());import requests
response = requests.post(
"https://messaging.mapi.mg/api/authentication/logout",
headers={"Authorization": "VOTRE_JETON"},
)
print(response.json())<?php
$curl = curl_init('https://messaging.mapi.mg/api/authentication/logout');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: VOTRE_JETON'],
]);
$response = curl_exec($curl);
echo $response;OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://messaging.mapi.mg/api/authentication/logout")
.addHeader("Authorization", "VOTRE_JETON")
.post(RequestBody.create(new byte[0], null))
.build();
try (Response response = client.newCall(request).execute()) {
System.out.println(response.body().string());
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://messaging.mapi.mg/api/authentication/logout");
request.Headers.TryAddWithoutValidation("Authorization", "VOTRE_JETON");
var response = await client.SendAsync(request);
Console.WriteLine(await response.Content.ReadAsStringAsync());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://messaging.mapi.mg/api/authentication/logout", nil)
req.Header.Set("Authorization", "VOTRE_JETON")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
fmt.Println(string(data))
}{
"status": true,
"message": "Logged out successfully"
}{
"status": false,
"message": "Already logged out"
}{
"status": false,
"message": "Authentication credentials required"
}Référence
Réponses & erreurs
Toutes les réponses sont en JSON. Le booléen status vaut true en cas de succès ; en cas d'échec il vaut false et le détail figure dans message ou result.
| Statut HTTP | Signification |
|---|---|
200 OK | Requête traitée avec succès. |
401 Unauthorized | Jeton absent, expiré ou révoqué, identifiants de connexion invalides, ou session déjà fermée. |
409 Conflict | Numéro de destinataire mal formé, ou solde d'unités épuisé. |
422 Unprocessable Entity | Paramètre manquant ou invalide : fichier absent ou d'un type non accepté, message absent, code OTP introuvable ou déjà utilisé. |
Codes d'erreur
Certaines erreurs précisent leur cause avec un champ numérique code :
| code | Endpoint | Signification |
|---|---|---|
1 | /authentication/login | Identifiant ou mot de passe incorrect. |
4 | /otp/verify | Code OTP introuvable. |
5 | /otp/verify | Code OTP déjà utilisé. |
status et code, jamais sur le texte des messages : il peut être en français ou en anglais et évoluer.Prêt à intégrer Mapi ?
Créez votre compte pour obtenir vos identifiants, ou contactez l'équipe pour être accompagné dans votre intégration.