Documentation J+SERVICES Guides Référence API

Contrat API — Bascule d’un client vers J+RADIUS

Le bouton « migrer ce client en mode RADIUS », et ce que le gérant voit pendant que ça se passe.

📱 Dev de l’application : le briefing d’intégration pas à pas — écrans, pièges, protocole de test des 7 routes — est sur /docs/CONTRAT-API-MIGRATION-RADIUS-APP. Ce document-ci reste la référence des champs.

Base : https://live.jmoai.net/api/v1/radius


1. Le fait qui rend cette bascule nécessaire

[!WARNING] Sur RouterOS, le hotspot consulte d’abord sa base locale et n’interroge RADIUS que si l’usager n’y figure pas.

Poser use-radius=yes en laissant les comptes locaux ne bascule donc rien : les clients continuent de s’authentifier en local, RADIUS n’est jamais appelé, et aucune erreur n’est levée.

Mesuré le 2026-08-29 : 8 707 comptes locaux sur le routeur de référence, et radacct à 874 lignes pour tout le parc, dont zéro authentification hotspot. Des déploiements avaient pourtant réussi. La bascule était inerte.

Le retrait des comptes locaux n’est donc pas un nettoyage de confort : c’est l’étape qui fait la bascule.

Le temps déjà consommé n’est pas perdu pendant l’import : l’uptime lu sur le MikroTik est déduit de Max-All-Session avant l’écriture RADIUS. Exemple : un voucher de 2 h avec 1 h déjà utilisée est publié avec 1 h restante. Limite importante : cette règle prépare la prochaine authentification RADIUS ; une session déjà ouverte en local reste pilotée par le routeur jusqu’à sa reconnexion, son expiration locale ou une coupure contrôlée.


2. Les six étapes, dont une seule est irréversible

1. inventaire         on regarde avant de promettre
2. sauvegarde         rapatriée ET vérifiée, sinon on s'arrête
3. import vers RADIUS  et on COMPTE ce qui est arrivé
4. configuration      le routeur parle au serveur, la confirmation est armée
5. retrait du local   ← IRRÉVERSIBLE, exige trois preuves
6. première connexion  une vraie authentification, avec le nom de l'usager

Les étapes 1 à 4 se rejouent sans dommage. La 5 exige trois preuves :

Preuve Comment elle est obtenue
SAUVEGARDE_CONFIRMEE des octets rapatriés, pas un simple retour sans exception
IMPORT_RADIUS_CONFIRME inventaire paginé du serveur RADIUS, complete: true
ROUTEUR_JOIGNABLE l’inventaire a répondu

Et un import incomplet bloque le retrait : des comptes non importés puis retirés, ce sont des tickets vendus qui disparaissent.

Si le retrait est refusé, la bascule s’arrête proprement : le routeur reste en double (local + RADIUS), le gérant continue de vendre, personne ne perd rien.


3. GET /admin/migration/{allocationId}/inventaire

Lecture seule. Aucune écriture, aucun engagement.

{
  "allocation_id": "uuid",
  "nas_id": "uuid",
  "routeur": "HH40AF7YVNP",
  "licence_active": true,
  "tunnel_joignable": true,
  "comptes_locaux": 8707,
  "espace_flash_mo": 36,
  "autorise": true,
  "motif": null,
  "motif_texte": null,
  "avertissements": []
}
motif Sens
LICENCE_RADIUS_INACTIVE pas de licence J+RADIUS active
ROUTEUR_INJOIGNABLE le routeur ne répond pas
INVENTAIRE_ILLISIBLE comptes non lisibles — ce n’est pas zéro compte
TROP_DE_COMPTES_POUR_UNE_BASCULE_AUTOMATIQUE plus de 15 000 : lot par lot, pas en un clic
ESPACE_FLASH_INSUFFISANT moins de 25 Mo : la sauvegarde échouerait

comptes_locaux: null n’est pas 0. Le serveur refuse dans ce cas. Une application qui afficherait « rien à migrer » sur un inventaire illisible enverrait un administrateur basculer à l’aveugle un routeur qui porte huit mille comptes.


4. POST /admin/migration/{allocationId}

{ "retirer_local": true }

retirer_local: false fait tout sauf le retrait : utile pour préparer une migration, ou laisser un routeur en double le temps d’observer.

{
  "success": true,
  "allocation_id": "uuid",
  "nas_id": "uuid",
  "statut": "ARMEMENT",
  "etapes": [
    { "etape": "SAUVEGARDE", "etat": "OK", "comptes": 8707 },
    { "etape": "CONFIG", "etat": "OK" },
    { "etape": "IMPORT", "etat": "OK", "importes": 8707 },
    {
      "etape": "VERIFICATION",
      "etat": "PARTIEL",
      "par_etat": { "PRET": 6412, "EPUISE_LOCAL": 852, "ABSENT_VOUCHER": 3 },
      "importes": 1829,
      "realignes": 374,
      "bloquants": 3,
      "retirables": 7210
    },
    {
      "etape": "RETRAIT_LOCAL",
      "etat": "PARTIEL",
      "retires": 7210,
      "restant_local": 42,
      "bloquants": 3
    },
    { "etape": "ARMEMENT", "etat": "OK" }
  ],
  "retires": 8706,
  "importes": 8707
}

RETRAIT_LOCAL peut valoir REFUSE avec un motif — la bascule s’est arrêtée là, et c’est un succès partiel, pas un échec : rien n’a été détruit.

Vérification compte par compte (depuis le 2026-09-22). Avant tout retrait, chaque compte local est confronté à son voucher RADIUS et à FreeRADIUS. Les comptes absents sont importés, le temps que RADIUS offrait en trop (import antérieur à la consommation) est ramené au reste local, puis tout est relu. Ne sont retirés que les comptes prouvés (PRET ou EPUISE_LOCAL), hors session en cours et sans temps en trop, par identifiant routeur et par lots recomptés. Les autres restent en local et continuent de fonctionner : PARTIEL signale qu’il en reste. Rejouer la bascule retire ceux devenus prouvés entre-temps. VERIFICATION en ECHEC arrête avant tout retrait.

États bloquants : ABSENT_VOUCHER, ABSENT_FREERADIUS, MOT_DE_PASSE_DIFFERENT, MOT_DE_PASSE_ILLISIBLE, VOUCHER_INUTILISABLE, TEMPS_RABOTE, DUREE_ABSENTE_RADIUS (le routeur limite la durée, RADIUS non). Tout écart de temps, dans un sens ou dans l’autre, est réaligné sur le reste local avant le retrait. PROFIL_DIFFERENT et DESACTIVE_LOCAL ne bloquent pas mais ne sont jamais retirés automatiquement.

Le compte default-trial est toujours épargné : il est créé par RouterOS et son retrait casse l’essai gratuit du hotspot.

Code d’erreur Sens
MIGRATION_BACKUP_FAILED / MIGRATION_BACKUP_EMPTY sauvegarde impossible ou vide — rien n’a été touché
MIGRATION_DEPLOY_FAILED configuration RADIUS refusée — rien n’a été supprimé
MIGRATION_COUNT_FAILED comptage impossible — retrait annulé
MIGRATION_REMOVE_STALLED le routeur ne retire plus rien — des comptes subsistent
MIGRATION_VERIFY_INCOMPLETE inventaire FreeRADIUS incomplet — aucun retrait

Le retrait est vérifié par comptage avant/après, pas par le retour de la commande : un retrait partiel qui passerait pour complet laisserait des comptes locaux, et ce sont eux qui masquent RADIUS.


4 bis. POST /admin/migration/{allocationId}/retour — revenir en local

Le bouton inverse. Remet les comptes hotspot sur le routeur, puis coupe RADIUS.

{ "couper_radius": true }

couper_radius: false remet les comptes sans couper RADIUS : le routeur reste en double le temps de vérifier que tout répond avant de trancher.

Roaming MAC

POST /vouchers/:voucherId/bind-mac lie un MAC au voucher du même client. Le serveur RADIUS ne crée pas de compte MAC : il résout la liaison vers le voucher encore valide et vérifie le tenant du NAS. La liaison est retirée quand le voucher est supprimé, suspendu ou épuisé.

L’API ne modifie jamais login-by sur un routeur à cette occasion. Le MAC-login est un opt-in explicite du propriétaire du NAS ; sans cet opt-in, les comptes et l’authentification existants restent inchangés.

Reconnexion douce d’une session locale

POST /hotspot/handoff-local prépare une session Hotspot locale déjà ouverte pour la bascule vers le voucher RADIUS correspondant. Le serveur lit la session active sur le MikroTik, récupère son MAC, lie ce MAC au voucher, déduit l’uptime local du temps restant, puis peut couper la session locale si cut_local: true est demandé.

{
  "allocation_id": "uuid",
  "username": "TM-E5DCDE",
  "cut_local": false
}

La coupure est acceptée uniquement si le profil /ip/hotspot/profile du serveur actif contient mac-cookie ou mac dans login-by. Sinon la réponse est 409 HANDOFF_AUTO_RECONNECT_NOT_READY et aucune session locale n’est supprimée. mac-cookie favorise le retour sur le même routeur; mac permet le MAC-login RADIUS si le format MAC du NAS et le profil sont configurés pour cela. La reconnexion quasi immédiate est donc une possibilité vérifiée, jamais une garantie aveugle: le client peut encore subir une brève nouvelle authentification selon son appareil, son cookie et le réseau.

Réponses et refus importants:

Code Sens
PRET_RECONNEXION_DOUCE MAC liée, temps restant calculé, aucune coupure demandée
SESSION_LOCALE_COUPÉE_RECONNEXION_ATTENDUE session locale supprimée après contrôle du profil
LOCAL_HOTSPOT_SESSION_NOT_FOUND aucune session locale correspondant au username
LOCAL_SESSION_MAC_MISSING la session active ne fournit pas de MAC exploitable
RADIUS_VOUCHER_NOT_RECONNECTABLE voucher supprimé, désactivé ou épuisé
HANDOFF_AUTO_RECONNECT_NOT_READY login-by ne contient ni mac-cookie ni mac

Le service ne modifie pas login-by, ne crée pas de compte Hotspot local et ne prétend pas que la session est déjà authentifiée par RADIUS. Il prépare et vérifie uniquement le passage. Le temps déjà consommé sur la session locale est conservé dans le calcul du temps restant.

{
  "success": true,
  "statut": "REVENU_EN_LOCAL",
  "comptes_restaures": 8706,
  "sauvegarde_datee_du": "2026-08-29T20:00:00Z",
  "ecart_avec_radius": {
    "connu": true,
    "ecart": 53,
    "avertissement": "Le serveur cloud porte 53 compte(s) de plus que la sauvegarde : ce sont des tickets vendus depuis la bascule. Ils ne seront PAS restaurés sur le routeur."
  },
  "etapes": [
    { "etape": "LECTURE_SAUVEGARDE", "etat": "OK", "comptes": 8707, "a_restaurer": 8706 },
    { "etape": "PROFILS", "etat": "OK", "profils": 18 },
    { "etape": "COMPTES", "etat": "OK", "poses": 8706 },
    { "etape": "VERIFICATION", "etat": "OK", "comptes": 8706 },
    { "etape": "RADIUS_COUPE", "etat": "OK" }
  ]
}

Ce qui n’est JAMAIS rejoué

[!WARNING] Le fichier .backup binaire n’est jamais restauré. Il porte toute la configuration — pare-feu, adresses, routes, peers WireGuard, tunnel de gestion. Le rejouer à distance rendrait le routeur muet une fois sur deux, et on perdrait le chemin d’accès en même temps que la possibilité de réparer.

Seuls les profils et les comptes hotspot sont remis. Rien d’autre n’est touché.

L’ordre, et pourquoi il ne s’inverse pas

  1. Profils d’abord — un compte dont le profil manque échoue, et fait tomber son lot entier.
  2. Comptes par lots de 40, commandes idempotentes : rejouer après un échec partiel ne lève pas « already have such name ».
  3. Vérification par comptage.
  4. use-radius=no en dernier. Couper RADIUS avant d’avoir remis les comptes laisserait le hotspot sans aucune base d’authentification — tous les clients dehors pendant la restauration. Si la restauration échoue ou reste incomplète, RADIUS n’est pas coupé : le hotspot garde une base qui répond.

L’entrée /radius n’est pas retirée : la laisser ne coûte rien et permet de rebasculer sans tout reconfigurer.

Ce que le retour ne peut pas rattraper — à afficher

La sauvegarde date d’avant la bascule. Les tickets vendus depuis vivent dans RADIUS et ne sont pas dans ce fichier : ecart_avec_radius.avertissement les compte. Inversement, les tickets déjà consommés depuis y figurent encore et redeviendront utilisables.

On ne corrige pas ça automatiquement — toute tentative ferait pire. On le dit, chiffre à l’appui, et l’administrateur tranche.

Code d’erreur Sens
RESTORE_NO_BACKUP aucune sauvegarde des comptes pour ce routeur
RESTORE_BACKUP_UNREADABLE sauvegarde illisible — refus, plutôt que restaurer zéro compte
RESTORE_BACKUP_EMPTY la sauvegarde ne contient aucun compte restaurable
RESTORE_USERS_FAILED restauration interrompue — RADIUS n’a pas été coupé
RESTORE_INCOMPLETE moins de comptes que prévu — RADIUS reste actif

5. Ce que le gérant reçoit, étape par étape

Notifications de type RADIUS_MIGRATION, une par étape :

Étape Message
SAUVEGARDE « Sauvegarde de votre configuration (8 707 comptes) — faite. »
IMPORT « Vos 8 707 comptes ont été copiés dans le serveur cloud. »
RETRAIT_LOCAL « 8 706 comptes locaux retirés — ils vivent maintenant dans le cloud. »
ARMEMENT « En attente de la première connexion d’un de vos clients. »
ECHEC « La bascule s’est arrêtée : … Rien n’a été supprimé sur votre routeur. »

Puis, type RADIUS_PREMIERE_AUTH, les cinq premières connexions réelles :

« Connexion 1/5 validée ✅ — TM-C4D835 vient de se connecter via le serveur cloud sur J+Pro Ax3. La bascule fonctionne. »

C’est la preuve que le gérant attend. Un « service opérationnel » sans nom d’usager ne prouve rien — c’est exactement ce qu’affichait l’ancien flux pendant que le hotspot continuait de répondre en local. Au-delà de cinq, le système se tait : il a compris qu’il a compris.

La confirmation est automatique depuis le 2026-08-29 (cron RADIUS_MIGRATION_WATCH, toutes les 3 min). Auparavant elle exigeait un clic d’administrateur, et personne ne le faisait.


6. GET /affluence — ce que le gérant lit sur son réseau

Lecture ouverte au client : c’est son réseau.

{
  "disponible": true,
  "connexions": 128,
  "clients": 34,
  "refus": 3,
  "pic": { "heure": 19, "connexions": 41 },
  "connexions_par_client": 3.8,
  "par_heure": [0, 0, "…"],
  "boucles": []
}

Le relevé de fin de journée

Chaque soir à 20 h UTC (21 h au Bénin), type RADIUS_AFFLUENCE :

« 128 connexion(s) réussie(s) aujourd’hui sur J+Pro Ax3, pour 34 client(s). Pic de fréquentation vers 19 h. 3 connexion(s) refusée(s). »

Seuls les clients qui ont eu de l’activité sont notifiés. Envoyer « 0 connexion » chaque soir à un gérant dont le routeur dort est le meilleur moyen de lui faire couper les notifications — et il ne verrait plus les vraies.


7. Réglages

Variable Défaut Effet
RADIUS_MIGRATION_WATCH_ENABLED true confirmation auto + premières connexions, toutes les 3 min
RADIUS_READY_AUTO_CONFIRM_ENABLED true (était false, et aucune cron ne l’appelait)
RADIUS_AFFLUENCE_REPORT_ENABLED true relevé de fin de journée à 20 h UTC