Table of Contents
API en direct
Deux surfaces distinctes, et la distinction compte.
| Surface | Qui appelle | Authentification |
|---|---|---|
/api/... (hote borne, relaye aussi sur l'hote back-office) |
la borne, un client anonyme | aucune |
/admin/api/... sur l'hote back-office |
un equipier, un outil de test | session + jeton anti-falsification (CSRF) |
L'hote de la borne ne relaie que /api vers le serveur d'application : le
back-office est hors d'atteinte depuis l'origine borne, par construction.
Essayer sans rien installer
curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/health
curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/categories
curl -s https://corentin-wakdo-admin.stark.a3n.fr/api/products
Ces trois appels repondent en JSON sans compte.
Le contrat
docs/api/conventions.md
— les conventions : enveloppe {data, error}, codes de statut, compteur total
sur les listes (pas de pagination), gestion des conflits. C'est le document de reference.
docs/adr/0017-api-admin-json.md — pourquoi une API JSON d'administration a ete ajoutee a cote du back-office rendu cote serveur, et ce qu'elle garantit.
Demonstration avec Postman, Bruno ou Insomnia
La collection couvre les 57 routes de l'API d'administration (/admin/api/…), dont, depuis le
2026-09-28, l'historique des mouvements d'un ingredient
(GET /admin/api/ingredients/{id}/movements), le modele d'import, l'apercu d'import
sans ecriture (?dry_run=1) et l'etat de sante detaille (GET /admin/api/health).
Les deux collections sont livrees, generees depuis un script pour rester alignees sur le code :
- docs/api/wakdo-admin.postman_collection.json
- docs/api/bruno
- docs/api/demo-api.md — le mode d'emploi de la demonstration
Les deux collections portent 91 requetes, chacune avec une verification de sa reponse.
Pour Insomnia, la collection se construit a la main en suivant le meme scenario
(docs/api/demo-api.md, section 8) : les collections Postman et Bruno servent de
reference, pas de copie.
La session s'ouvre en JSON : POST /admin/api/auth/login avec un corps JSON
pose le cookie de session et rend le jeton anti-falsification (CSRF) a placer dans
l'en-tete des ecritures. Plus besoin de passer par le formulaire HTML pour tester.
GET /admin/api/auth/me dit qui est connecte, POST /admin/api/auth/logout ferme.
Les identifiants des comptes de demonstration : docs/demo/comptes-demo.md
Depuis le back-office : la page Sante
Connecte, /admin/health rassemble au meme endroit :
- l'etat de l'application et sept appels reels, dont cinq refus attendus (401, 403, 415...) arretes avant toute ecriture, avec le detail de chaque reponse (statut, en-tetes utiles, corps JSON indente) ;
- la carte des 158 routes, lue dans le routeur et non recopiee, rangee par
action : la page qui affiche le formulaire (GET), l'envoi du formulaire, la meme
action dans l'API JSON. Une action a souvent deux ou trois routes : afficher puis
envoyer sont deux requetes, un formulaire HTML ne connait que GET et POST (d'ou
POST …/deletecote back-office etDELETEcote API), et l'API refait les memes actions en JSON, avec les memes permissions et le meme code personnel ; - le trajet d'un appel, couche par couche, avec des reponses reelles : une
lecture est envoyee au serveur quand on lance l'appel ; une ecriture n'est jamais
envoyee depuis la page, et le trajet montre la reponse obtenue pour de vrai sur une
pile de test jetable (date et commit de la capture affiches). Les 158 routes y sont
capturees en succes, et 670 refus obtenus sur 689 tentes (les autres sont affiches
« non reproduit » avec le code observe), par
tests/e2e/health-capture.spec.js; - une console de lecture : toute route
GET, un champ par parametre, appelee avec la session de la page. Aucune ecriture n'est possible depuis la page, et c'est le code qui le refuse, pas seulement la liste deroulante ; - une connexion de demonstration qui montre la vraie reponse de
POST /admin/api/auth/loginet son jeton, sans remplacer la session de qui regarde la page, puis donne la sequencecurla rejouer dans Postman, Bruno ou Insomnia.
docs/journal/2026-09-27--console-page-sante.md — les garde-fous et comment ils sont verifies en vrai navigateur.
Construire une requete soi-meme
Cinq questions, dans l'ordre ; chaque reponse remplit une case de Postman, Bruno ou Insomnia.
- Sur quoi j'agis ? Le chemin. Une collection (
/admin/api/ingredients) ou un element (/admin/api/ingredients/12) : l'identifiant va dans le chemin, jamais apres un?. Les parametres apres?sont des options (?dry_run=1). - Quel geste ? La methode. Lire :
GET. Creer :POSTsur la collection, sans identifiant. Modifier :PUTsur l'element, qui remplace l'element entier (lire d'abord l'element, copier sondata, changer ce qu'il faut). Supprimer ou desactiver :DELETE. Une action metier :POST …/{id}/action. - Qui suis-je ? Le cookie de session, pose par
POST /admin/api/auth/loginet renvoye tout seul par l'outil. Ne rien mettre dans l'onglet d'authentification. - Est-ce une ecriture ? Alors l'en-tete
X-CSRF-Token, avec lecsrf_tokenrenvoye par la connexion (il change a chaque connexion). - Quelles donnees ? Le corps, en JSON (
Content-Type: application/json). Pour une action sensible, ajouterpin_emailetpindans le corps, meme pour unDELETE.
Lire la reponse
Le code dit a quelle etape la requete a ete arretee :
| Code | Etape | Ce qu'on corrige |
|---|---|---|
| 404 | routeur | le chemin, ou un identifiant qui n'existe pas (exception : un numero de commande inconnu sur /admin/api/orders/... rend 403, pas 404, pour ne pas distinguer un numero inconnu d'un canal non visible) |
| 405 | routeur | la methode : le chemin existe, pas pour ce geste |
| 401 | session | se reconnecter |
403 FORBIDDEN |
permission | ce compte n'a pas le droit |
403 CSRF_INVALID |
jeton | en-tete X-CSRF-Token absent ou d'une ancienne connexion |
400 INVALID_JSON |
corps | JSON mal ecrit, ou racine qui n'est pas un objet |
| 415 | corps | l'en-tete Content-Type n'est pas application/json |
422 VALIDATION_ERROR |
validation | lire error.fields : chaque ligne dit quoi mettre |
422 PIN_INVALID |
code personnel | pin_email ou pin faux |
| 409 | base | l'etat ne le permet pas (ingredient qui a un historique, commande annulee ou deja livree, emplacement de menu deja commande) |
Cote back-office, une adresse inconnue affiche une page « Page introuvable » ; l'API, elle, repond toujours dans son enveloppe JSON.
Sur l'API de la borne (POST /api/orders), le refus d'une commande porte un code precis :
EMPTY_ORDER, INVALID_QUANTITY (1 a 20 par ligne), TOO_MANY_ITEMS (50 lignes),
ORDER_TOO_LARGE (50 articles), OPTION_UNAVAILABLE (option de menu indisponible pour le
format choisi). Sur /admin/api/orders, les memes refus sortent en 422 VALIDATION_ERROR,
le message dans error.fields.items.
Le cycle complet en ligne de commande
scripts/curl-e2e-json-login.sh — connexion, lecture, creation, modification, suppression, deconnexion, avec une verification a chaque etape. Les identifiants se passent par l'environnement, ils ne sont pas ecrits dans le script.
Ce que l'API refuse, et pourquoi
Le controle d'acces verifie une permission, pas un nom de role : un role personnalise dote des bonnes permissions ouvre les memes fonctions sans changement de code. La matrice complete : docs/demo/matrice-rbac.md
Les actions sensibles demandent en plus un code personnel (4 a 12 chiffres ; ceux
des comptes de demonstration en ont 4), verifie independamment de la session :
annulation d'une commande, suppression d'un produit ou d'un menu, changement de prix
ou de TVA d'un produit, confirmation d'un import quand il change un prix, inventaire et ajustement de stock,
creation, modification, desactivation, effacement et remise a zero du code personnel
d'un compte, creation et modification d'un role. La liste qui fait foi est la
colonne PIN de
src/app/Health/RouteSecurity.php.
La personne qui agit est inscrite dans la meme transaction que l'action : au journal
d'audit, ou, pour l'inventaire et l'ajustement, dans le mouvement de stock.
Import de produits par fichier
docs/api/import-produits.md — le format du fichier CSV attendu, le modele telechargeable depuis le back-office, et ce que l'apercu montre avant d'appliquer quoi que ce soit.