Documentation développeur
Guide complet pour intégrer les APIs SIFIP : authentification, appel des endpoints, gestion des erreurs, exemples Python et cURL.
Introduction
L'API SIFIP est une API REST sécurisée par OAuth2 (Bearer JWT). Toutes les requêtes utilisent
https://api.sifip.io (ou http://api.localtest.me en dev local).
Chaque appel est facturé selon votre offre (forfait ou paiement à l'usage).
Les réponses sont au format JSON avec Content-Type: application/json.
Prérequis
- Créez un compte partenaire (gratuit, 1 minute).
- Connectez-vous et créez une application dans votre dashboard.
- Récupérez votre clé privée RSA 2048 (affichée une seule fois, sauvegardez-la).
- Souscrivez à une offre commerciale qui couvre les APIs souhaitées.
Choisir une offre
Standard
Number Verification, KYC Tenure, SIM Swap. Idéal pour onboarding.
99 €/mois ou 0.03 €/appel
Pro
Standard + Device Swap, Number Recycling, Call Forwarding, Verified Caller.
499 €/mois ou 0.02 €/appel
Premium
Pro + Fraud Engine (scoring consolidé).
2 500 €/mois ou 0.015 €/appel
Flux d'authentification OAuth2
SIFIP utilise le flow client_credentials avec Private Key JWT (RFC 7523). Aucun secret partagé n'est transmis : votre application signe une assertion JWT avec sa clé privée RSA.
- Votre application signe une assertion JWT (RS256) avec sa clé privée.
- Elle l'envoie au token endpoint Keycloak.
- Keycloak vérifie la signature (avec la clé publique enregistrée) et émet un access token.
- Votre application utilise ce token (Bearer) pour appeler les APIs SIFIP.
Clés privées RSA
Lors de la création de votre application sur le portail SIFIP, une paire de clés RSA 2048 est générée. La clé privée vous est affichée une seule fois au format PEM (PKCS#8). Sauvegardez-la dans un coffre fort (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, etc.).
-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQ... ... -----END PRIVATE KEY-----
Sécurité : ne loggez jamais la clé privée, ne la commitez pas en git, ne l'envoyez jamais par email. Si elle est compromise, supprimez l'application et créez en une nouvelle.
Obtenir un access token
Étape 1 : générer l'assertion JWT
Construisez un JWT signé RS256 avec les claims suivants :
{
"iss": "votre-client-id",
"sub": "votre-client-id",
"aud": "https://auth.sifip.io/realms/sifip/protocol/openid-connect/token",
"jti": "uuid-unique-par-requête",
"exp": "now + 60 secondes",
"iat": "now"
}
Étape 2 : échanger l'assertion contre un access token
POST https://auth.sifip.io/realms/sifip/protocol/openid-connect/token
avec Content-Type: application/x-www-form-urlencoded :
grant_type=client_credentials &client_id=votre-client-id &client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer &client_assertion=<JWT signé ci-dessus> &scope=sifip:sim-swap
Réponse
{
"access_token": "eyJhbGc...",
"expires_in": 900,
"token_type": "Bearer",
"scope": "sifip:sim-swap"
}
Exemple complet en Python
import time, uuid, jwt, requests
CLIENT_ID = "sifip-app-mybank-abc123"
PRIVATE_KEY = open("private.pem").read()
TOKEN_URL = "https://auth.sifip.io/realms/sifip/protocol/openid-connect/token"
now = int(time.time())
assertion = jwt.encode({
"iss": CLIENT_ID, "sub": CLIENT_ID,
"aud": TOKEN_URL,
"jti": str(uuid.uuid4()),
"exp": now + 60, "iat": now,
}, PRIVATE_KEY, algorithm="RS256")
r = requests.post(TOKEN_URL, data={
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": assertion,
"scope": "sifip:sim-swap"
})
access_token = r.json()["access_token"]
Appeler une API SIFIP
Avec l'access token, appelez les APIs avec le header Authorization: Bearer <token> :
r = requests.post(
"https://api.sifip.io/sim-swap/vwip/check",
headers={
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"X-Request-ID": str(uuid.uuid4()) # optionnel mais recommandé pour tracer
},
json={"phoneNumber": "+261340000000", "maxAge": 240}
)
print(r.json()) # {"swapped": false}
L'en-tête X-Request-ID est propagé dans nos logs et facilite le support en cas d'incident.
SIM Swap
Détecte si la SIM associée à un MSISDN a été échangée récemment (red flag fraude).
Scope requis : sifip:sim-swap
POST /sim-swap/vwip/retrieve-date
Date du dernier SIM swap.
{ "phoneNumber": "+261340000000" }
↓
{ "latestSimChange": "2026-04-15T10:23:00Z" }
POST /sim-swap/vwip/check
Booléen : SIM swap dans les maxAge heures ?
{ "phoneNumber": "+261340000000", "maxAge": 240 }
↓
{ "swapped": false }
Number Verification
Vérifie que le MSISDN fourni correspond bien à celui de l'utilisateur (silent auth réseau).
Scope requis : sifip:number-verify
POST /number-verification/vwip/verify
{ "phoneNumber": "+261340000000" }
↓
{ "devicePhoneNumberVerified": true }
GET /number-verification/vwip/device-phone-number
Renvoie le MSISDN du device authentifié.
{ "devicePhoneNumber": "+261340000000" }
KYC Tenure
Vérifie qu'un abonné est client de l'opérateur depuis au moins une date donnée (signal fort de confiance).
Scope requis : sifip:identity
POST /kyc-tenure/vwip/check-tenure
{ "phoneNumber": "+261340000000", "tenureDate": "2023-01-01" }
↓
{ "tenureDateCheck": true, "contractType": "PAYM" }
Device Swap
Détecte un changement récent de device (IMEI) sur le même MSISDN.
Scope requis : sifip:device-swap
POST /device-swap/vwip/retrieve-date
{ "phoneNumber": "+261340000000" }
↓
{ "latestDeviceChange": "2026-03-22T08:15:00Z" }
POST /device-swap/vwip/check
{ "phoneNumber": "+261340000000", "maxAge": 240 }
↓
{ "swapped": false }
Number Recycling
Détecte si le titulaire d'un numéro a changé depuis une date donnée (évite d'envoyer des SMS à un nouveau titulaire).
Scope requis : sifip:number-recycling
POST /number-recycling/vwip/check
{ "phoneNumber": "+261340000000", "specifiedDate": "2024-10-31" }
↓
{ "phoneNumberRecycled": false }
Call Forwarding Signal
Détecte si un service de call forwarding est actif (red flag pour interception OTP).
Scope requis : sifip:call-forwarding
POST /call-forwarding-signal/vwip/unconditional-call-forwardings
{ "phoneNumber": "+261340000000" }
↓
{ "active": false }
POST /call-forwarding-signal/vwip/call-forwardings
{ "phoneNumber": "+261340000000" }
↓
["inactive"] // ou ["unconditional","conditional_busy"]
Verified Caller
Crée une pré-annonce d'appel sortant (anti-spoofing) avec affichage de marque chez le destinataire.
Scope requis : sifip:verified-caller
POST /verified-caller/vwip/pre-announce
{
"callingParticipant": "+261340000000",
"calledParticipant": "+261349999999",
"strategy": "BRAND_DISPLAY",
"timeToLive": 60,
"dynamicDisplayName": "ACME Bank"
}
↓ 201 Created
{ "preAnnouncementId": "uuid", "expiresAt": "2026-05-25T10:00:00Z" }
Fraud Engine
Scoring consolidé multi-signaux (telco + device + biométrie + comportemental + géo). Renvoie une décision APPROVE / REVIEW / CHALLENGE / REJECT.
Scope requis : sifip:fraud-engine
POST /fraud-engine/score
{
"transactionId": "txn-987654",
"useCase": "PAYMENT",
"customer": { "firstName": "Ali", "lastName": "Rahal" },
"telecom": { "msisdn": "+216 55111222", "simSwap": { "enabled": true } },
"device": { "ipAddress": "1.2.3.4", "rooted": false, "emulator": false },
"biometrics": { "livenessScore": 0.95, "deepfakeProbability": 0.02 },
"behavioral": { "typingSpeed": 215, "abnormalNavigation": false }
}
↓
{
"decision": "APPROVE",
"riskLevel": "LOW",
"scores": {
"fraudScore": 12,
"identityConfidenceScore": 94,
"telecomTrustScore": 91,
"biometricTrustScore": 97,
"deviceTrustScore": 89
},
"riskSignals": ["VALID_MSISDN","KNOWN_DEVICE","BIOMETRIC_MATCH"],
"recommendation": { "action": "ALLOW", "stepUpAuthentication": false }
}
Codes d'erreur
| Code HTTP | Code SIFIP | Description | Comment résoudre |
|---|---|---|---|
| 400 | INVALID_ARGUMENT | Body JSON malformé ou paramètre invalide | Vérifier le format selon le swagger |
| 400 | OUT_OF_RANGE | Valeur hors plage (ex. maxAge > 2400h) | Réduire la valeur |
| 401 | UNAUTHENTICATED | Token absent, expiré ou signature invalide | Obtenir un nouveau token |
| 401 | token revoked | Token révoqué via /admin/tokens/revoke | Obtenir un nouveau token (jti différent) |
| 403 | PERMISSION_DENIED | Token valide mais scope insuffisant | Souscrire à une offre qui inclut le scope requis |
| 404 | NOT_FOUND | Ressource ou identifier inconnu | Vérifier le clientId / phoneNumber |
| 404 | IDENTIFIER_NOT_FOUND | MSISDN non reconnu par l'opérateur | Vérifier le format E.164 et l'opérateur |
| 422 | SERVICE_NOT_APPLICABLE | API non disponible pour ce MSISDN | Opérateur non supporté pour cette API |
| 422 | MISSING_IDENTIFIER | phoneNumber manquant (2-legged) | Ajouter phoneNumber dans le body |
| 422 | UNNECESSARY_IDENTIFIER | phoneNumber fourni en 3-legged | Ne pas fournir phoneNumber (déjà dans le token) |
| 429 | QUOTA_EXCEEDED | Quota mensuel atteint | Upgrade d'offre |
| 429 | TOO_MANY_REQUESTS | Rate limit gateway dépassé | Backoff exponentiel et retry |
| 500 | INTERNAL | Erreur serveur SIFIP | Réessayer après quelques secondes, contacter le support si persistant |
| 503 | UNAVAILABLE | Maintenance temporaire | Attendre, voir status.sifip.io |
Format de réponse d'erreur
{
"status": 400,
"code": "INVALID_ARGUMENT",
"message": "Client specified an invalid argument, request body or query param."
}
Rate limiting
Chaque appel API renvoie 3 en-têtes :
X-RateLimit-Limit: limite par minute selon votre offreX-RateLimit-Remaining: appels restants dans la fenêtre couranteX-RateLimit-Reset: secondes restantes avant reset
Limites par offre :
| Offre | req/min |
|---|---|
| Unitaire | 60 |
| Standard | 100 |
| Pro | 300 |
| Premium | 1 000 |
Sandbox vs Production
Deux environnements sont disponibles :
| Env | URL | Données | Facturation |
|---|---|---|---|
| Sandbox | https://sandbox-api.sifip.io | Stub (random) | Aucune |
| Production | https://api.sifip.io | Réelles (via opérateurs) | Selon offre |
Vos clientId et clé privée sont les mêmes dans les deux environnements. Le scope est aussi identique.