Référence
Interface de programmation
Transmettre des valeurs par l'API entrante /api/v1 (clés, signature HMAC, points d'accès, erreurs) et réutiliser les données ouvertes publiques (CSV, graphiques intégrables).
La PNISAN offre deux voies d'échange automatisé. L'API entrante permet à un système sectoriel (santé, agriculture, hydraulique, éducation, protection sociale) de transmettre des valeurs d'indicateurs sans passer par un fichier : chaque requête est authentifiée par une clé et signée. Les données ouvertes du portail permettent à quiconque de télécharger les valeurs validées en CSV et d'intégrer des graphiques sur un autre site, sans compte. Cette page décrit les deux.
API entrante : principes #
- Adresse de base :
https://pnisan.bi/api/v1. Échanges en JSON, encodage UTF-8, messages d'erreur en français. - Chaque envoi entre dans la zone de transit (canal API entrante), puis suit exactement le circuit de l'import de fichier : normalisation, contrôles qualité, arbitrage des sources, validation provinciale et nationale.
- Les valeurs entrent au statut « Soumis » : aucune n'est publiée sans validation humaine. Une valeur déjà validée n'est jamais remplacée par l'API ; l'écriture est consignée comme conflit et attend la décision d'un administrateur.
- L'effectif est facultatif par cette voie : une valeur sans effectif est acceptée et signalée en avertissement.
Obtenir une clé #
Les clés sont émises dans la console, écran Clés d'accès applicatif, par un super-administrateur ou un administrateur national.
| Champ | Rôle |
|---|---|
| Organisation | Système ou institution émettrice |
| Identifiant | Public, de la forme pnin_ suivi de 24 caractères, à transmettre dans l'en-tête X-Cle |
| Secret | 48 caractères, affiché une seule fois à l'émission : « Copiez ce secret maintenant et transmettez-le au système émetteur par un canal sûr. Il ne sera plus jamais affiché. » |
| Indicateurs autorisés | Codes d'indicateurs ; vide : tout le référentiel |
| Territoires autorisés | Codes de provinces ou de communes, ou national ; une province couvre ses communes ; vide : tout le territoire |
| Requêtes par minute | Plafond de la clé, 60 par défaut |
| Expire le | Date d'expiration facultative |
Un secret perdu ne se récupère pas : l'administrateur émet une nouvelle clé et révoque l'ancienne (bouton Révoquer, action définitive). Émission, modification, révocation et chaque envoi reçu sont consignés au Journal d'audit.

Signer une requête #
Trois en-têtes sont obligatoires sur chaque requête :
| En-tête | Contenu |
|---|---|
X-Cle |
Identifiant de la clé |
X-Horodatage |
Instant de la requête, en secondes depuis le 1er janvier 1970 UTC |
X-Signature |
HMAC-SHA256 en hexadécimal minuscule de la chaîne à signer, avec le secret pour clé |
La chaîne à signer compte quatre lignes séparées par un saut de ligne, sans saut de ligne final :
METHODE
CHEMIN
HORODATAGE
EMPREINTE_DU_CORPS
METHODE:GETouPOST, en majuscules ;CHEMIN: chemin tel qu'envoyé, paramètres compris, par exemple/api/v1/valeurs;HORODATAGE: la valeur exacte deX-Horodatage;EMPREINTE_DU_CORPS: SHA-256 hexadécimal du corps brut ; pour unGETsans corps, empreinte de la chaîne vide (e3b0c442...b855).
Le corps signé doit être exactement celui qui est envoyé : sérialisez le JSON une seule fois, signez ces octets, envoyez ces octets. L'horodatage doit être à moins de cinq minutes de l'horloge du serveur (synchronisez l'émetteur par NTP), et une même signature ne sert qu'une fois : pour renvoyer un lot, signez-le de nouveau avec un nouvel horodatage.
Points d'accès #
| Méthode et adresse | Usage |
|---|---|
POST /api/v1/valeurs |
Déposer un lot de 500 valeurs au plus |
GET /api/v1/ingestions/{id} |
Suivre un envoi : statut, compteurs, erreurs, avertissements, conflits, répartition des valeurs par statut de validation. Une clé ne voit que ses propres envois |
GET /api/v1/referentiel |
Indicateurs du périmètre de la clé : code, intitulé, système, unité, périodicité, désagrégations, statut |
GET /api/v1/territoires |
Provinces et communes du périmètre (codes, P-codes, codes ISIBU, découpage), et admission des valeurs nationales (code BDI) |
Champs d'une valeur #
| Champ | Obligatoire | Contenu |
|---|---|---|
indicateur |
Oui | Code du référentiel, par exemple NUT-01 |
code_territoire ou territoire |
Non | Code (P-code, code d'ancienne province, code ISIBU) ou nom ; absent : valeur nationale |
periode_debut |
Oui | AAAA-MM-JJ, JJ/MM/AAAA, AAAA-MM ou AAAA |
periode_fin |
Non | Même format ; absente : fin du mois de début |
periode |
Non | Libellé affiché, par exemple 2022-2023 |
valeur |
Oui | Nombre, point ou virgule décimale |
effectif |
Non | Entier |
desagregation, unite |
Non | Clé de désagrégation ; unité déclarée, confrontée au référentiel |
source_donnee, note_source, source_url |
Non | Publication d'origine, note, lien http ou https complet |
Le lot peut porter un libelle et une reference (255 caractères chacun). La réponse 201 renvoie l'ingestion créée avec ses erreurs ligne par ligne : une ligne en erreur n'empêche pas les autres de passer. Un lot identique déjà reçu avec la même clé n'est pas retraité : réponse 200 avec "deja_recu": true.
Exemple de requête #
CLE="pnin_xxxxxxxxxxxxxxxxxxxxxxxx"
SECRET="..."
CHEMIN="/api/v1/valeurs"
CORPS='{"libelle":"Suivi nutritionnel, janvier 2026","valeurs":[{"indicateur":"NUT-01","code_territoire":"BI-GI","periode_debut":"2026-01-01","periode_fin":"2026-01-31","valeur":42.5,"effectif":1200}]}'
HORODATAGE=$(date +%s)
EMPREINTE=$(printf '%s' "$CORPS" | openssl dgst -sha256 -r | cut -d' ' -f1)
SIGNATURE=$(printf 'POST\n%s\n%s\n%s' "$CHEMIN" "$HORODATAGE" "$EMPREINTE" \
| openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1)
curl -sS -X POST "https://pnisan.bi$CHEMIN" \
-H "Content-Type: application/json" \
-H "X-Cle: $CLE" -H "X-Horodatage: $HORODATAGE" -H "X-Signature: $SIGNATURE" \
--data-binary "$CORPS"
Utilisez --data-binary et non -d, qui modifierait le corps signé. Réponse type :
{
"message": "Envoi reçu : 1 valeur(s) en attente de validation, 0 ligne(s) en erreur, 0 conflit(s).",
"deja_recu": false,
"ingestion": { "id": 42, "statut": "en_validation", "lignes_total": 1, "lignes_valides": 1, "score_qualite": 100 }
}
En Python, la signature s'écrit :
corps = json.dumps(charge, ensure_ascii=False).encode("utf-8")
horodatage = str(int(time.time()))
chaine = "\n".join(["POST", "/api/v1/valeurs", horodatage, hashlib.sha256(corps).hexdigest()])
signature = hmac.new(SECRET.encode(), chaine.encode(), hashlib.sha256).hexdigest()
Codes d'erreur #
Les erreurs ont la forme {"erreur": {"code": "...", "message": "..."}} : le code est stable, destiné au programme ; le message s'adresse à l'exploitant.
| Statut | Codes | Cause fréquente |
|---|---|---|
| 400 | json_invalide, requete_invalide |
Corps qui n'est pas un JSON valide |
| 401 | entetes_absents, cle_inconnue, cle_revoquee, cle_expiree, horodatage_invalide, horodatage_hors_fenetre, signature_invalide |
Horloge décalée, secret erroné, corps modifié après signature |
| 404 | ressource_introuvable |
Ingestion d'une autre clé |
| 405 | methode_non_autorisee |
Méthode incorrecte |
| 409 | requete_rejouee |
Signature déjà utilisée : signez de nouveau |
| 422 | valeurs_absentes, trop_de_lignes, libelle_invalide, reference_invalide |
Lot vide ou de plus de 500 valeurs |
| 429 | trop_de_requetes |
Plafond de la clé atteint ; respectez l'en-tête Retry-After |
| 500 | erreur_interne |
L'envoi n'est pas enregistré, vous pouvez le renouveler |
Les envois reçus se consultent dans la console, écran Ingestion des données, canal API entrante. La spécification OpenAPI 3 complète est tenue par l'équipe technique (docs/openapi.yaml).
Données ouvertes du portail #
Sans compte ni clé, le portail publie les valeurs validées au niveau national, au-dessus du seuil de masquage :
| Adresse | Contenu |
|---|---|
https://pnisan.bi/indicateurs.csv |
Catalogue : une ligne par indicateur avec la dernière valeur nationale, sa période, son statut, son évolution, sa source, le lien de la source, la date de validation et l'adresse de la fiche. Filtres recherche, systeme (par exemple ?systeme=nutrition) et periodicite |
https://pnisan.bi/indicateurs/{id}/donnees.csv |
Série d'un indicateur : valeurs nationales et provinciales, période, désagrégation, unité, statut, source, lien de la source, date de validation |
https://pnisan.bi/prix-des-denrees/donnees.csv |
Relevés de prix des denrées, un relevé par ligne, avec marché, P-code, coordonnées, éditeur, licence, adresse source et copie archivée ; filtre ?denree= |
https://pnisan.bi/integrer/indicateurs/{id} |
Graphique d'un indicateur à intégrer dans une page (balise iframe) |
https://pnisan.bi/integrer/carte |
Carte du Tableau de bord à intégrer |
Les fichiers CSV sont encodés en UTF-8 avec marque d'ordre des octets, séparés par des points-virgules, avec la virgule comme séparateur décimal. Le numéro {id} d'un indicateur figure dans l'adresse de sa Fiche indicateur.