1. Introduction
CI Connect expose une API B2B pour lancer des vérifications KYC en mode machine-to-machine (M2M).
Objectif :
- Créer une requête de vérification pour un citoyen
- Suivre son statut
- Récupérer le résultat si le citoyen accepte le partage de données
Cette documentation est faite pour les équipes backend qui intègrent CI Connect côté serveur.
Pré-requis d'accès :
- Disposer d'un compte client vérifié sur la plateforme CI Connect
- Disposer d'une application active (sandbox ou production)
- Récupérer les clés de l'application :
apiKeyetsecretKey
api/v1Préfixe API — ex :
https://api.example.com/api/v1/data-requests2. Démarrage rapide
Étape 1 — Variables d'environnement
bash
export CI_CONNECT_BASE_URL="https://api.example.com"
export CI_CONNECT_API_KEY="pk_live_xxx_ou_pk_test_xxx"
export CI_CONNECT_SECRET_KEY="sk_xxx"Étape 2 — Premier appel (création d'une requête)
bash
curl -X POST "$CI_CONNECT_BASE_URL/api/v1/data-requests" \
-H "Content-Type: application/json" \
-H "X-API-Key: $CI_CONNECT_API_KEY" \
-H "X-Secret-Key: $CI_CONNECT_SECRET_KEY" \
-d '{
"nni": "12345678901", // ou "phoneNumber": "+225000000000"
"requestedFields": ["FIRST_NAME", "LAST_NAME", "BIRTH_DATE"]
}'Exemple de réponse succès :
json
{
"statusCode": 201,
"message": "Success",
"data": {
"requestId": "6f7b3cf6-0a34-4d2d-9af9-0fd2b6d31d74",
"status": "PENDING",
"cost": {
"base": 100,
"fieldSurcharges": 50,
"fieldSurchargeDetails": {
"BIRTH_DATE": 50
},
"total": 150,
"currency": "XOF"
},
"expiresAt": "2026-03-31T10:15:00.000Z",
"consentTimerSeconds": 10,
"createdAt": "2026-03-31T09:15:00.000Z"
},
"timestamp": "2026-03-31T09:15:00.000Z"
}Étape 3 — Exploiter le requestId
- Appeler
GET /api/v1/data-requests/:id/statuspour suivre le statut - Appeler
GET /api/v1/data-requests/:id/resultpour récupérer les données - Si un callback est configuré sur l'application, attendre la notification puis appeler
GET /api/v1/data-requests/:id/resultpour récupérer les données
3. Authentification (Sécurité)
Authentification M2M :
- Header obligatoire
X-API-Key - Header obligatoire
X-Secret-Key
Important : les routes M2M n'utilisent pas
Bearer JWT. Sans les 2 headers, la réponse est 401.Exemple HTTP :
http
GET /api/v1/data-requests/6f7b3cf6-0a34-4d2d-9af9-0fd2b6d31d74 HTTP/1.1
Host: api.example.com
X-API-Key: pk_live_xxx
X-Secret-Key: sk_xxxRègles d'environnement :
- Une clé
pk_test_...doit appeler les routes sandbox (/api/v1/sandbox/...) - Une clé production (
pk_live_...) doit appeler les routes hors sandbox - En production, le client doit avoir l'accès production activé (
hasProductionAccess), sinon403
3.1 Champs de données possibles
Les valeurs de
requestedFields doivent respecter 2 règles :- Utiliser des codes de champs existants dans CI Connect
- Rester dans la liste
allowedDataFieldsautorisée sur votre application
Pour récupérer la liste des champs actifs, utilisez :
GET /api/v1/reference-data/data-fields— optionnel : ?search=FIRST pour filtrerbash
curl -X GET "$CI_CONNECT_BASE_URL/api/v1/reference-data/data-fields" \
-H "Content-Type: application/json"Exemple de réponse :
json
{
"statusCode": 200,
"message": "Success",
"data": [
{
"id": "6c4f2a5f-d4e7-4c85-8db2-6f7f0211a31f",
"code": "FIRST_NAME",
"label": "Prénom",
"category": "IDENTITY",
"surcharge": 0,
"isActive": true,
"sortOrder": 1,
"createdAt": "2026-03-01T10:00:00.000Z"
},
{
"id": "6d087f8f-9f13-4d16-a0c5-d8d1a2d0c4a7",
"code": "LAST_NAME",
"label": "Nom",
"category": "IDENTITY",
"surcharge": 0,
"isActive": true,
"sortOrder": 2,
"createdAt": "2026-03-01T10:00:00.000Z"
}
],
"timestamp": "2026-03-31T09:45:00.000Z"
}Tableau de référence des champs :
| Code | Label | Surcharge | Montant |
|---|---|---|---|
Note : ce tableau est chargé dynamiquement depuis
GET /api/v1/reference-data/data-fields — il reflète toujours l'état réel de la plateforme.4. Endpoints / Routes (M2M)
POST
/api/v1/data-requestsCréer une requête KYC en production (statut initial
PENDING).Headers :
X-API-Key, X-Secret-KeyBody :
nniouphoneNumber(string, obligatoire, 11 chiffres)requestedFields(string[], optionnel, min 1)
Notes :
- Si
requestedFieldsest omis, tous les champs autorisés de l'application sont utilisés - Si un champ demandé n'est pas autorisé pour l'application, la requête est rejetée (
400)
json
{
"nni": "12345678901", // ou "phoneNumber": "+225000000000"
"requestedFields": ["FIRST_NAME", "LAST_NAME"]
}GET
/api/v1/data-requests/:id/statusRécupérer le statut d'une requête KYC.
Headers :
X-API-Key, X-Secret-KeyChamps de réponse :
requestId, status, cost, expiresAt, respondedAt, deviceConfig, createdAtStatuts possibles :
PENDING, APPROVED, REJECTED, EXPIRED, FAILED, CALLBACK_SENT, CALLBACK_FAILEDjson
{
"statusCode": 200,
"message": "Success",
"data": {
"requestId": "6f7b3cf6-0a34-4d2d-9af9-0fd2b6d31d74",
"status": "PENDING",
"cost": {
"base": 100,
"fieldSurcharges": 50,
"fieldSurchargeDetails": {
"BIRTH_DATE": 50
},
"total": 150,
"currency": "XOF"
},
"expiresAt": "2026-03-31T10:15:00.000Z",
"respondedAt": null,
"deviceConfig": null,
"createdAt": "2026-03-31T09:15:00.000Z"
},
"timestamp": "2026-03-31T09:20:00.000Z"
}GET
/api/v1/data-requests/:id/resultRécupérer le résultat de la requête (mode polling).
Headers :
X-API-Key, X-Secret-KeyComportement :
dataest renseigné quand le statut estAPPROVEDouCALLBACK_SENT- Sinon
datavautnull
json
{
"statusCode": 200,
"message": "Success",
"data": {
"requestId": "6f7b3cf6-0a34-4d2d-9af9-0fd2b6d31d74",
"status": "APPROVED",
"respondedAt": "2026-03-31T09:18:15.000Z",
"deviceConfig": {
"source": "mobile"
},
"data": {
"FIRST_NAME": "Moussa",
"LAST_NAME": "Traore",
"BIRTH_DATE": "1990-05-15"
}
},
"timestamp": "2026-03-31T09:21:00.000Z"
}SANDBOXPOST
/api/v1/sandbox/data-requestsCréer une requête sandbox synchrone (sans contact CII).
Body :
nniouphoneNumber(string, obligatoire, valeur libre en sandbox)requestedFields(string[], obligatoire, min 1)simulatedAction(obligatoire :APPROVEDouREJECTED)
json
{
"nni": "FAKE-NNI-001" // ou "phoneNumber": "+225000000000" ,
"requestedFields": ["FIRST_NAME", "LAST_NAME"],
"simulatedAction": "APPROVED"
}5. Flux de traitement
Client B2B
↓POST /api/v1/data-requests
↓PENDING
↓Consentement Citoyen
↓Statut Final
↓
Callback (si configuré)
↓
GET /data-requests/:id
↓
GET /data-requests/:id/result
En pratique :
- À la création, une requête est en
PENDING - En production, le citoyen doit consentir pour que les données soient partagées
- Si l'application a une
callbackUrl, CI Connect envoie une notification quand le résultat est disponible - Sans callback, faire du polling sur
statusetresult - La charge utile KYC est disponible via
GET .../resultquand la requête est approuvée
⚠
Callback reçu ? Vous devez encore appeler
GET /api/v1/data-requests/:id/resultLe payload du callback ne contient pas les données KYC — il signale uniquement que la requête est finalisée. Pour récupérer les champs demandés (prénom, nom, date de naissance, etc.), vous devez impérativement effectuer un appel GET /api/v1/data-requests/:id/result après réception du callback.
6. Erreurs & Status Codes
Codes HTTP fréquents
| Code | Description |
|---|---|
200 | Lecture de statut/résultat |
201 | Création de requête |
400 | Payload invalide, champ non autorisé, endpoint non adapté |
401 | Auth manquante ou invalide |
403 | Accès refusé (compte inactif, mauvaise clé, accès prod non activé) |
404 | Requête introuvable |
429 | Limite de débit atteinte |
500 | Erreur interne |
Format d'erreur
json
{
"statusCode": 403,
"message": "L'accès à la production n'est pas activé. Utilisez /sandbox/data-requests pour tester.",
"timestamp": "2026-03-31T09:30:00.000Z"
}Exemple d'erreur de validation :
json
{
"statusCode": 400,
"message": "Validation failed",
"errors": {
"validationErrors": [
"Le NNI doit contenir exactement 11 chiffres"
]
},
"timestamp": "2026-03-31T09:30:00.000Z"
}Messages possibles côté auth/API key :
Clé API requiseSecret API requisClé API invalide ou application suspendueSecret API invalideClé API de production utilisée sur une route sandbox...Clé API sandbox utilisée sur une route de production...Accès production non activé. Utilisez /sandbox/data-requests pour tester.
7. SDK / Librairies
SDK Node officiel :
@asernum/ci-connect-sdk-nodePrérequis : Node.js
>= 18Installation
bash
pnpm add @asernum/ci-connect-sdk-nodeInitialisation
typescript
import { CiConnectSDK } from "@asernum/ci-connect-sdk-node";
const sdk = new CiConnectSDK({
apiKey: process.env.CI_CONNECT_API_KEY!,
secretKey: process.env.CI_CONNECT_SECRET_KEY!,
baseUrl: process.env.CI_CONNECT_BASE_URL!, // ex: https://api.example.com
mode: "auto", // auto | live | sandbox
timeoutMs: 10_000,
retries: 2,
});Premier appel
typescript
const created = await sdk.createRequest({
nni: "12345678901",
requestedFields: ["FIRST_NAME", "LAST_NAME"],
});
console.log(created.requestId, created.status);Statut + résultat
typescript
const status = await sdk.getRequestStatus(created.requestId);
const result = await sdk.getRequestResult(created.requestId);Polling automatique
typescript
const finalResult = await sdk.waitForResult(created.requestId, {
intervalMs: 2_000,
timeoutMs: 60_000,
stopOnStatuses: ["APPROVED", "REJECTED"],
});Sandbox via SDK
typescript
const sandbox = new CiConnectSDK({
apiKey: process.env.CI_CONNECT_API_KEY!,
secretKey: process.env.CI_CONNECT_SECRET_KEY!,
baseUrl: process.env.CI_CONNECT_BASE_URL!,
mode: "sandbox",
});
const sandboxResult = await sandbox.createRequest(
{ nni: "FAKE-NNI-001", requestedFields: ["FIRST_NAME"] },
{ simulatedAction: "APPROVED" },
);Erreurs typées
AuthenticationError(401)AuthorizationError(403)NotFoundError(404)ValidationError(400/422)RateLimitError(429)
8. Bonnes pratiques
- Commencer en sandbox avant toute intégration production
- Utiliser un timeout explicite et une stratégie de retry avec backoff côté client
- Ne jamais exposer
secretKeycôté frontend - Stocker les clés dans un gestionnaire de secrets (vault, variables chiffrées, rotation régulière)
- Privilégier
callbackpour les notifications, et garderpollingen fallback - Après réception d'un callback, toujours appeler
GET .../result— le callback ne contient pas les données KYC - Journaliser
requestIdà chaque étape pour faciliter le support - Gérer explicitement les statuts terminaux (
APPROVED,REJECTED,EXPIRED,FAILED,CALLBACK_SENT,CALLBACK_FAILED)
Rate limit :
- Sandbox : 20 requêtes / 60 secondes par clé API
- Global API : 100 requêtes / 60 secondes (configurable)
9. FAQ / Dépannage
Pourquoi j'ai 403 en production ?
Cela arrive typiquement si :
- Vous utilisez une clé sandbox (
pk_test_...) sur/data-requestsproduction - Votre compte n'a pas encore
hasProductionAccess - Le compte client est suspendu/rejeté
Pourquoi data est null dans GET /result ?
data est null tant que le statut n'est pas en état de partage effectif (requête encore PENDING, rejetée, expirée, ou en échec).Quelle différence entre status et result ?
GET /data-requests/:id: état et métadonnées de la requêteGET /data-requests/:id/result: même logique de suivi, avec en plus le champdataquand disponible
Comment simuler un cas en sandbox ?
Appelez
POST /api/v1/sandbox/data-requests avec :- Un
nnifictif - La liste
requestedFields simulatedActionàAPPROVEDouREJECTED
Le SDK met-il en place le polling automatiquement ?
Oui, via
waitForResult(requestId, options) avec intervalle et timeout configurables.J'ai reçu le callback — où sont les données ?
Le callback ne transporte pas les données KYC. Dès réception, appelez
GET /api/v1/data-requests/:id/result pour obtenir le champ data avec les informations du citoyen.