Contrat API — Panneau de gestion de consommation
Base : https://live.jmoai.net/api/v1/radius.
Le principe en une phrase : par routeur, le gérant active un panneau, choisit un mode (AUTO régulé, FUP seul, ou bridage manuel) et peut migrer les usagers de ses profils commerciaux vers la gestion cloud RADIUS ; le serveur décide et maintient, sans que l’application reste ouverte.
┌──────────── PUT /consumption-panel/{nasId} ────────────┐
│ enabled + mode │
▼ │
AUTO_REGULE ── toutes les 10 min : budget × usagers connectés ──► plafond du routeur
FUP_ONLY ── aucun plafond global ; chaque ticket suit le FUP de son profil RADIUS
BRIDAGE ── réglages manuels (mêmes champs que PUT /bandwidth/program)
POST /consumption-panel/{nasId}/migration ── profils commerciaux (ou cochés)
→ vérification compte par compte → import cloud → retrait des seuls comptes prouvés
Le panneau n’écrit rien directement sur le routeur. Il oriente trois mécanismes existants,
chacun gardant ses propres garanties : le moteur de bridage (seul à poser des files, sous le
commentaire JSBRIDAGE), le FUP par ticket du worker RADIUS, et la bascule vérifiée.
Droits
| Route | Exige |
|---|---|
GET /consumption-panel/{nasId} |
licence RADIUS + Suivi du Matériel |
PUT /consumption-panel/{nasId} |
licence RADIUS + Suivi du Matériel + STARLINK X |
GET /consumption-panel/{nasId}/profiles |
licence RADIUS |
POST /consumption-panel/{nasId}/migration |
licence RADIUS |
Mêmes droits, mêmes refus 403 (ENTITLEMENT_REQUIRED + required_feature) que le bridage :
voir CONTRAT-API-BRIDAGE. Lire GET /clients/entitlements/bridage à l’ouverture de l’écran
pour savoir quoi proposer. Les écritures sont limitées à 5 appels/minute ; le quota global
reste 100 requêtes / 15 min par IP, partagé par TOUTE l’application — ne pas sonder l’état en
boucle serrée (une lecture toutes les 30 s pendant une migration suffit).
GET /consumption-panel/{nasId}
L’état complet de l’écran, en un appel. Répond aussi pour un routeur jamais réglé
(enabled: false, mode: null).
{
"nas_id": "0b5c…",
"routeur": "J+Pro Ax3",
"enabled": true,
"mode": "AUTO_REGULE",
"revision": 3,
"wan_down_mbps": 200,
"wan_up_mbps": 20,
"auto": {
"plancher_confort_kbps": 1500,
"plancher_survie_kbps": 512,
"jours_confort_min": 21,
"facteur_pointe": 2
},
"budget": {
"quota_gb": 5000,
"recharges_gb": 0,
"billing_start_day": 9,
"cycle_debut": "2026-09-09"
},
"decision": {
"regime": "CONFORT",
"brider": true,
"debit_mbps": 37.5,
"par_usager_kbps": 1500,
"usagers_actifs": 25,
"besoin_confort_mbps": 37.5,
"besoin_survie_mbps": 12.8,
"rythme_cycle_mbps": 30.9,
"rythme_confort_mbps": 44.1,
"reste_gb": 3120.4,
"consomme_gb": 1879.6,
"jour_gb": 142.3,
"jours_restants_cycle": 17,
"jours_confort_restants": 8,
"rythme_observe_gb_jour": 144.6,
"jours_tenables": 21.6,
"capacite_adaptative": {
"mode": "SIMULATION",
"applique": false,
"statut": "DONNEES_INSUFFISANTES",
"echantillons": 0,
"requis": 6
},
"change": false
},
"decision_le": "2026-09-22T19:42:00Z",
"bridage": {
"enabled": true,
"mode": "ADAPTIVE",
"limit_mbps": 200,
"plafond_pilote_mbps": 37.5,
"etat_souhaite": "ON",
"etat_constate": "ON",
"derniere_erreur": null
},
"fup": [
{
"profile_id": "7d1e…",
"nom": "500F-4d",
"prix_xof": 500,
"debit_down_kbps": 50000,
"debit_up_kbps": 15000,
"fup_enabled": true,
"fup_limit_mb": 5120,
"fup_speed_down_kbps": 2048,
"fup_speed_up_kbps": 512
}
],
"migration": {
"scope": "ALL_COMMERCIAL",
"profils": ["500F-4d", "200F-1d"],
"statut": "ARMEMENT",
"etapes": [{ "etape": "VERIFICATION", "etat": "PARTIEL", "retirables": 5002, "bloquants": 3 }],
"maj_le": "2026-09-22T19:30:11Z"
},
"prerequis": [
{ "code": "BUDGET_DECLARE", "ok": true },
{ "code": "CAPACITE_LIEN", "ok": true },
{ "code": "MOTEUR_DE_FILES", "ok": true }
]
}
Comment l’afficher
- En tête d’écran :
decision.regimeetdecision.jours_tenables. « Qualité préservée, le budget tient environ 21 jours » est ce que le gérant vient chercher ; « 1 879 Go sur 5 000 » ne lui apprend rien qu’il ne voie dans son compte Starlink. decision.par_usager_kbps= la part de chacun sous le plafond : plafond du routeur divisé par les usagers connectés. Sur le routeur, la file « reste » est partagée à parts égales entre les adresses clientes actives (PCQ, voirCONTRAT-API-BRIDAGE), et la messagerie passe devant. Tant que le TEST-01 (deux clients réels) n’est pas validé sur le routeur pilote, afficher « environ 1,5 Mb/s par personne ».budget.coefficient_calibrage= rapport entre le compteur Starlink et notre mesure WAN (1,05 tant qu’aucun relevé n’a été saisi,coefficient_calibre: false). Les décisions du mode AUTO portent surdecision.consomme_estime_starlink_gb, jamais sur la mesure brute.decision.capacite_adaptativeest un diagnostic en simulation :appliquerestefalse. Il sert à observer la future borne de capacité issue de la latence et de la charge, sans remplacer le plafond budgétaire posé sur le routeur.bridage.etat_constatepeut rester en retard surplafond_pilote_mbpsde 5 minutes au plus : la pose sur le routeur a lieu à la réconciliation du moteur. Ce n’est pas une panne.prerequis[].ok: false→ afficher ce qu’il manque au lieu d’un régime :BUDGET_DECLARE(forfait non saisi),CAPACITE_LIEN(débit du lien non saisi),MOTEUR_DE_FILES(programme de bridage absent ou désactivé).fupliste les profils RADIUS du compte et leur FUP. Il se modifie par la route existantePUT /profiles/{profileId}(CONTRAT-API-PROFILS-RADIUS) — le panneau ne duplique pas cette écriture.
Ce qui n’est PAS une erreur
decision: null: le mode AUTO n’a pas encore tranché (prérequis manquant, ou premier calcul en attente d’une mesure). Afficher « calcul en cours », pas une erreur.decision.jours_tenables: null: moins d’un jour écoulé dans le cycle. Une projection bâtie sur trois heures dirait n’importe quoi.decision.regime: "LIBRE"avecbrider: false: le budget est large, aucun plafond n’est posé.decision.capacite_adaptative.statut: "DONNEES_INSUFFISANTES": la simulation n’a pas encore six échantillons utilisables. Ce n’est pas une erreur et ça ne bloque pas le mode AUTO.migration.statut: null: aucune migration n’a jamais été lancée sur ce routeur.
PUT /consumption-panel/{nasId}
Active, change de mode ou désactive. Idempotent : rejouer le même corps ne produit que le
même état. Réponse 200 : l’état complet, identique à GET.
Mode AUTO régulé
{
"enabled": true,
"mode": "AUTO_REGULE",
"wan_down_mbps": 200,
"wan_up_mbps": 20,
"budget": { "quota_gb": 5000, "billing_start_day": 9 },
"auto": {
"plancher_confort_kbps": 1500,
"plancher_survie_kbps": 512,
"jours_confort_min": 21,
"facteur_pointe": 2
},
"essential_classes": ["dns", "meta", "telegram", "x"],
"essential_share": 0.25
}
| Champ | Obligatoire | Règle |
|---|---|---|
wan_down_mbps |
oui | capacité descendante réelle du lien, > 0 et ≤ 10 000 |
wan_up_mbps |
non | > 0 et ≤ 10 000 |
budget.quota_gb |
oui* | volume du cycle (5 000 pour 5 To). *Facultatif si le forfait est déjà déclaré |
budget.billing_start_day |
non | 1–31, jour de renouvellement Starlink (défaut : celui déjà déclaré, sinon 1) |
auto.plancher_confort_kbps |
non | 256–20 000 (défaut 1 500) |
auto.plancher_survie_kbps |
non | 128–5 000 (défaut 512), jamais supérieur au plancher de confort |
auto.jours_confort_min |
non | 7–31 (défaut 21) |
auto.facteur_pointe |
non | 1–4 (défaut 2) |
essential_classes, essential_share |
non | identiques à PUT /bandwidth/program |
Un paramètre hors bornes est refusé, jamais rabattu en silence : le gérant doit savoir que son réglage n’est pas celui qu’il croit.
Le forfait saisi est le même que CONTRAT-API-STARLINK-PRIORITY. Le lissage Starlink de ce
routeur est suspendu (is_active: false) : le mode AUTO le remplace, deux pilotes ne peuvent
pas écrire le même plafond. Les recharges déclarées s’ajoutent au budget et ne se reportent
pas au cycle suivant.
Mode FUP seul
{ "enabled": true, "mode": "FUP_ONLY" }
Aucun plafond global : le programme de bridage éventuel est désactivé et le routeur nettoyé.
Chaque ticket RADIUS suit le FUP de son profil — débit plein jusqu’au seuil, puis débit
réduit. Le FUP se règle profil par profil sur PUT /profiles/{profileId}.
⚠️ Le seuil FUP est figé sur le ticket au moment de la vente. Modifier le FUP d’un profil s’applique aux tickets vendus ensuite, pas à ceux déjà en circulation. C’est volontaire : ce qui a été vendu ne change pas après coup. Les usagers encore authentifiés en local ne sont pas concernés par le FUP RADIUS tant qu’ils ne sont pas migrés.
Mode bridage manuel
{
"enabled": true,
"mode": "BRIDAGE",
"bridage": {
"mode": "BOTH",
"limit_mbps": 10,
"schedule_weekdays": [2, 3],
"daily_quota_gb": 80,
"user_daily_quota_gb": 5,
"user_limit_mbps": 1
}
}
bridage accepte exactement les champs de PUT /bandwidth/program (sans nas_id), avec les
mêmes règles et les mêmes codes BW_* en cas de refus.
Désactiver
{ "enabled": false }
Retire le plafond piloté, désactive le programme de bridage créé pour le panneau (le routeur est nettoyé à la réconciliation suivante) et arrête le pilote AUTO. En mode FUP seul, les tickets déjà vendus gardent leur FUP.
Codes d’erreur (400 sauf mention)
| Code | Sens |
|---|---|
PANEL_ENABLED_REQUIRED |
enabled absent ou non booléen |
PANEL_MODE_INVALID |
mode inconnu |
PANEL_WAN_INVALID |
capacité hors ]0, 10 000] |
PANEL_WAN_REQUIRED |
mode AUTO sans wan_down_mbps |
PANEL_BUDGET_REQUIRED |
mode AUTO sans budget, et aucun forfait déjà déclaré |
PANEL_BILLING_DAY_INVALID |
jour de facturation hors 1–31 |
PANEL_AUTO_PARAM_INVALID |
réglage AUTO hors bornes ; details nomme le champ et ses bornes |
PANEL_BRIDAGE_REQUIRED |
mode BRIDAGE sans objet bridage |
PANEL_NAS_NOT_OWNED |
404 — le routeur n’appartient pas au compte |
BW_* |
refus du programme de bridage (voir CONTRAT-API-BRIDAGE) |
Le mode AUTO — comment il décide
Toutes les 10 minutes, pour chaque panneau AUTO actif :
reste = budget du cycle − consommé (WAN du routeur)
rythme(horizon) = reste × 8 000 ÷ (heures restantes jusqu'à l'horizon × 3 600) × facteur de pointe
besoin confort = usagers connectés × plancher de confort
besoin survie = usagers connectés × plancher de survie
C’est la méthode du budget pacing des régies publicitaires et du fair usage des opérateurs mobiles : ce qui reste, divisé par le temps qui reste, recalculé en boucle. Une surconsommation de pointe fait baisser les plafonds suivants, une sous-consommation les relève : aucun modèle de demande à deviner. Le facteur de pointe reconnaît que le trafic n’est pas uniforme — le plafond ne mord qu’aux heures chargées.
| Régime | Quand | Plafond du routeur | Le gérant lit |
|---|---|---|---|
LIBRE |
le rythme sur 30 j dépasse la capacité du lien | aucun | budget large |
CYCLE |
le rythme sur 30 j couvre le besoin confort | rythme sur 30 j | le budget tient jusqu’au renouvellement |
CONFORT |
30 j passeraient sous le confort, 21 j non | besoin confort | trop d’usagers pour 30 j : qualité gardée, ≥ 3 semaines |
PROTECTION_21J |
même 21 j passeraient sous le confort | rythme sur 21 j | débit réduit pour tenir 3 semaines |
PLANCHER |
même la survie ne tient pas 21 j | besoin survie, jamais moins | budget insuffisant : recharge conseillée |
BUDGET_EPUISE |
plus rien | besoin survie | jamais coupé : de quoi payer une recharge |
Passé le 21e jour du cycle, la promesse des trois semaines est tenue : le régime reste
CONFORT (qualité gardée) même si le budget doit finir avant le renouvellement.
Le plancher de confort de 1,5 Mb/s par usager permet la navigation, WhatsApp (messages, appels audio et vidéo) et la vidéo en définition standard — c’est le débit auquel les opérateurs mobiles plafonnent la vidéo sans dégrader l’usage courant. Le plancher de survie de 512 kb/s garde la messagerie et les appels audio. Sous tout plafond, le trafic essentiel (DNS, WhatsApp/Messenger/Instagram/Facebook, Telegram, X) passe en priorité 1 avec un débit garanti ; tout le reste attend son tour. Rien n’est jamais bloqué ni déconnecté.
Trois garde-fous à connaître pour ne pas s’étonner :
- Hystérésis de 20 % : dans un même régime, un plafond qui bouge de moins de 20 % n’est pas réécrit. Un changement de régime, lui, est toujours appliqué.
- Mesure aveugle ≠ zéro usager : si ni RADIUS, ni le routeur, ni l’agent QoE ne peuvent compter les connectés, la décision précédente reste en place.
- Délai de pose : jusqu’à 5 minutes entre la décision et le routeur.
Journal GET /bandwidth/activity?nas_id= : PANEL_ENABLED, PANEL_DISABLED,
PANEL_MODE_CHANGED, AUTO_REGULATED, AUTO_RELEASED, PANEL_MIGRATION_STARTED,
PANEL_MIGRATION_FINISHED. Une notification CONSUMPTION_PANEL_REGIME part une fois par
régime et par jour en entrant dans CONFORT, PROTECTION_21J, PLANCHER ou BUDGET_EPUISE
— jamais à chaque recalcul.
GET /consumption-panel/{nasId}/profiles
Les profils Hotspot du routeur, classés, pour l’écran de migration. Lit le routeur : prévoir quelques secondes.
{
"nas_id": "0b5c…",
"scope": "SELECTED",
"profils": [
{
"nom": "default",
"categorie": "DEFAUT",
"raison": "PROFIL_SYSTEME",
"prix": null,
"usagers_locaux": 0,
"selectionne": false
},
{
"nom": "500F-4d",
"categorie": "COMMERCIAL",
"raison": "ENTETE_MIKHMON",
"prix": 500,
"usagers_locaux": 4212,
"selectionne": true
},
{
"nom": "staff",
"categorie": "NON_COMMERCIAL",
"raison": "AUCUNE_PREUVE_DE_VENTE",
"prix": null,
"usagers_locaux": 3,
"selectionne": false
}
]
}
Un profil est COMMERCIAL dès qu’une preuve de vente existe : en-tête Mikhmon avec prix ou
validité (ENTETE_MIKHMON), métadonnées JSON de l’application avec un prix
(METADONNEES_APP), ou prix au catalogue RADIUS (CATALOGUE_RADIUS). default et
default-trial sont DEFAUT et ne sont jamais migrés. Les profils de sortie de bridage
(fup-*) sont NON_COMMERCIAL.
Afficher une case à cocher par profil, pré-cochée sur selectionne. Griser DEFAUT.
POST /consumption-panel/{nasId}/starlink-reading
Le gérant saisit le consommé du cycle affiché par l’application Starlink. Le serveur le rapproche de sa propre mesure WAN au même instant et calibre le coefficient appliqué aux décisions. Mêmes droits que la lecture du panneau ; 5 appels/minute.
{ "consomme_gb": 1080 }
Réponse 200 :
{
"nas_id": "0b5c…",
"declare_gb": 1080,
"mesure_gb": 1000,
"ecart_pct": 8,
"coefficient_brut": 1.08,
"coefficient_calibrage": 1.08,
"retenu": true,
"raison": "CALIBRE"
}
raison |
Sens |
|---|---|
CALIBRE |
relevé retenu ; lissé 50/50 avec le coefficient précédent |
DECLARE_INFERIEUR_A_LA_MESURE |
Starlink affiche moins que nous : ramené à 1,00 (saisie à vérifier) |
ECART_ANORMAL_PLAFONNE |
écart > 30 % : plafonné à 1,30 — appareils hors routeur ou erreur |
MESURE_TROP_FAIBLE |
moins de 20 Go mesurés dans le cycle : relevé gardé, non retenu |
Erreurs : PANEL_READING_INVALID (400), PANEL_BUDGET_REQUIRED (400, aucun forfait),
PANEL_MEASURE_UNAVAILABLE (503, mesure du routeur indisponible — réessayer).
Proposer la saisie une fois par semaine : c’est ce qui rapproche le budget piloté de la facture Starlink (objectif d’écart ≤ 2 %).
POST /consumption-panel/{nasId}/migration
Le bouton « migrer vers la gestion cloud ».
{ "scope": "ALL_COMMERCIAL" }
{ "scope": "SELECTED", "profiles": ["500F-4d", "staff"], "retirer_local": true }
| Champ | Règle |
|---|---|
scope |
ALL_COMMERCIAL (tous les profils commerciaux, rien d’autre) ou SELECTED |
profiles |
requis avec SELECTED ; noms exacts de GET …/profiles ; non commerciaux acceptés |
retirer_local |
défaut true. false = importer et vérifier sans rien retirer du routeur |
Réponse 202 — la migration part en arrière-plan :
{
"nas_id": "0b5c…",
"statut": "EN_COURS",
"scope": "ALL_COMMERCIAL",
"profils": ["500F-4d", "200F-1d"],
"suivi": "/consumption-panel/0b5c…"
}
202 veut dire « lancée », jamais « terminée ». Suivre migration.statut et
migration.etapes sur GET /consumption-panel/{nasId}, toutes les 30 s environ.
Ce que fait la migration, dans l’ordre
- Sauvegarde du routeur, rapatriée et vérifiée — sinon rien ne se passe.
- Configuration RADIUS du routeur (
use-radius=yesrelu sur l’appareil). - Vérification compte par compte des usagers des seuls profils choisis : chaque compte local est confronté à son ticket cloud et à FreeRADIUS. Les absents sont importés, le temps que le cloud offrait en trop (ticket importé avant d’être consommé) est réaligné, puis tout est relu.
- Retrait du routeur des seuls comptes prouvés, hors session en cours. Les autres restent en local et continuent de fonctionner.
États d’étape : OK, PARTIEL (il reste des comptes bloquants ou en session — relancer plus
tard les retirera s’ils sont devenus prouvés), REFUSE, ECHEC (rien n’a été retiré).
migration.statut: "ECHEC" s’accompagne d’un code : MIGRATION_BACKUP_FAILED,
MIGRATION_DEPLOY_FAILED, MIGRATION_VERIFY_INCOMPLETE, MIGRATION_REMOVE_STALLED —
détail dans CONTRAT-API-MIGRATION-RADIUS.
Codes d’erreur
| Code | Statut | Sens |
|---|---|---|
PANEL_MIGRATION_RUNNING |
409 | une migration est déjà en cours sur ce routeur |
PANEL_SCOPE_INVALID |
400 | scope inconnu |
PANEL_PROFILES_REQUIRED |
400 | SELECTED sans profil |
PANEL_PROFILE_UNKNOWN |
400 | profil absent du routeur ; details.profils les nomme |
PANEL_DEFAULT_PROFILE_FORBIDDEN |
400 | default ou default-trial coché ; details.profils |
PANEL_NO_COMMERCIAL_PROFILE |
400 | ALL_COMMERCIAL mais aucun profil commercial sur le routeur |
PANEL_ROUTER_UNREACHABLE |
409 | routeur sans accès distant |
Ce que le serveur garantit
- Un seul écrivain sur le routeur : le panneau n’y écrit jamais. Le moteur de bridage y
pose ses files (
/queue simple,/queue type, sousJSBRIDAGE) et rattache les profils Hotspot à ce plafond (parent-queue) ; il ne modifie aucun autre réglage des profils. Chaque pose est protégée par un retour arrière automatique posé sur le routeur (3 min). Une file mal réglée ralentit, elle ne rend jamais un routeur injoignable. - Le forfait de chaque usager est toujours respecté, plafond actif ou non : désactiver le panneau ne libère jamais un usager au-delà de son propre forfait.
- Routeurs v6 : pris en charge (par SSH), avec le même comportement que les v7.
- Jamais de coupure : aucun régime ne descend sous le plancher de survie, et la messagerie reste prioritaire sous tout plafond.
- Aucun compte perdu à la migration : un compte n’est retiré du routeur qu’après avoir été prouvé identique dans le cloud (nom, mot de passe, statut, temps restant).
- Une migration à la fois par routeur ; une migration bloquée plus de 2 h est considérée comme abandonnée et peut être relancée.
Ce qui n’est PAS une erreur
- Un usager déjà connecté n’est pas encore plafonné : le plafond s’applique à sa prochaine connexion (règle RouterOS). Ne pas afficher d’échec pendant ce délai.
- Des usagers restent hors plafond tant qu’ils sont gérés par un profil local du routeur : seuls les usagers gérés par RADIUS cloud (migration du panneau) sont tenus par le plafond. C’est une raison d’inviter le gestionnaire à migrer ses profils commerciaux.
Écarts assumés
- Le quota FUP n’est pas rétroactif. Le seuil est figé sur le ticket à la vente ; changer le FUP d’un profil ne réécrit pas les tickets vendus. Revenir dessus ferait changer après coup ce que le client a acheté.
- Le mode AUTO plafonne le routeur, pas chaque ticket. Poser un débit par ticket via RADIUS exigerait une ré-authentification de chaque client à chaque recalcul — des coupures en cascade toutes les dix minutes.
- Le partage égal exige le réseau du Hotspot. Les files visent le réseau client de chaque
serveur Hotspot actif (lu sur le routeur), pas
0.0.0.0/0: sur0.0.0.0/0, montant et descendant correspondraient tous deux à la cible et PCQ classerait par serveur distant au lieu de classer par client. Si ce réseau est illisible, le plan est posé sans partage égal (journalisé) plutôt qu’avec un partage faux. - Le coefficient Starlink n’est pas figé à +5 %. Il est calibré sur les relevés du gérant, borné entre 1,00 et 1,30 et lissé : un chiffre fixe ne vaudrait que par chance pour un site donné. Les octets mesurés restent bruts en base.
- Pas d’horizon choisi à la main. Le gérant règle les planchers et le minimum de jours ; le serveur choisit l’horizon (30 j ou 21 j) selon le monde connecté. Un horizon fixe forcerait soit à casser la qualité aux heures chargées, soit à finir le budget trop tôt.