Documentation J+SERVICES Guides Référence API

Contrat API — Forfaits Starlink Priority (quota, lissage, recharges)

Pour les clients basculés de force sur un forfait Priority quantifié après une restriction Starlink.

[!IMPORTANT] J+SERVICES ne vend pas cet abonnement. Starlink le vend au client. Nous avons deux rôles, et deux seulement :

  1. l’aider à exploiter ce forfait au mieux — étaler le quota pour qu’il tienne le mois ;
  2. lui dire, chiffres en main, si son mois a été rentable.

Aucun montant de ce contrat n’est encaissé par nous. Le champ facture_par vaut toujours STARLINK, et l’écran doit le répéter : un montant affiché sans cette mention laisse croire que le paiement se fait chez nous.

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


1. Ce que le serveur sait, et ce qu’il ne sait pas

[!WARNING] Nous n’avons aucune API vers Starlink. Ni pour lire le quota, ni pour acheter un bloc.

Trois nombres cohabitent, de trois natures différentes. L’application doit les distinguer à l’écran, sinon le gérant croira que nous lisons son compte Starlink :

Nombre Nature D’où il vient
forfait.base_quota_gb déclaré saisi par le gérant
consommation.cycle_gb mesuré par nous notre relevé sur le WAN du routeur
consommation.declare_gb relevé chez Starlink saisi par le gérant depuis son compte

Le champ consommation.source vaut toujours MESURE_JSERVICES. Affichez « estimation » à côté de la jauge, jamais « consommation Starlink ».

Pourquoi les deux mesures diffèrent : nous comptons ce qui passe par le WAN du MikroTik, Starlink compte ce qui passe par la parabole — donc aussi le trafic des appareils qui la partagent sans passer par le routeur. consommation.ecart_gb expose la différence dès que le gérant a saisi le chiffre de Starlink ; au bout de deux cycles, il connaît son facteur.


2. GET /starlink/priority/{nasId}/qos

{ "is_active": true, "base_quota_gb": 50, "billing_start_day": 9 }

Un NAS jamais configuré renvoie { "is_active": false, "base_quota_gb": null, "billing_start_day": 1 }.

3. PUT /starlink/priority/{nasId}/qos

{ "is_active": true, "base_quota_gb": 50, "billing_start_day": 9 }
{
  "success": true,
  "message": "Configuration IA mise à jour avec succès",
  "is_active": true,
  "base_quota_gb": 50,
  "billing_start_day": 9
}
Code Sens
STARLINK_QUOTA_INVALID quota absent, nul ou aberrant
STARLINK_BILLING_DAY_INVALID jour de facturation hors 1–31
MAC_NAS_NOT_OWNED ce NAS n’appartient pas à ce client

4. GET /starlink/priority/{nasId} — le tableau de bord

C’est cet appel qui alimente la page. Il porte le cycle, la consommation, la projection et l’état du pilotage.

{
  "configure": true,
  "is_active": true,
  "forfait": {
    "base_quota_gb": 50,
    "recharges_gb": 0,
    "quota_total_gb": 50,
    "billing_start_day": 9
  },
  "cycle": {
    "debut": "2026-08-09",
    "renouvellement": "2026-09-09",
    "jours_total": 31,
    "jours_restants": 10
  },
  "consommation": {
    "source": "MESURE_JSERVICES",
    "cycle_gb": 45,
    "jour_gb": 0.4,
    "restant_gb": 5,
    "pourcentage": 90,
    "declare_gb": null,
    "ecart_gb": null
  },
  "projection": {
    "rythme_gb_jour": 2.14,
    "jours_tenables": 2,
    "panne_seche_avant_renouvellement": true
  },
  "pilotage": {
    "debit_impose_mbps": 1,
    "dernier_calcul": "2026-08-29T14:07:00Z",
    "detail": { "motif": "ALLOCATION_DU_JOUR_ATTEINTE", "ideal_jour_gb": 0.5 },
    "moteur": "PRET"
  },
  "grille_recharge": [{ "gb": 50, "prix_xof": 9400 }, "…"]
}

projection est ce que le gérant vient chercher. « Au rythme actuel, épuisé dans 2 jours » lui permet d’agir ; « 45 Go sur 50 » ne lui apprend rien qu’il ne voie dans son compte Starlink. Mettez-la en tête de page, pas la jauge.


4 bis. Reconduction tacite — la règle à afficher au gérant

L’abonnement Starlink est le sien. Nous n’y avons aucun accès : s’il change de forfait ou ajoute des gigaoctets, nous ne l’apprenons que s’il nous le dit.

Sans mise à jour de sa part, le cycle repart à l’identique.

C’est la règle, et elle doit être écrite à l’écran. Le silence est une réponse — encore faut-il que le gérant le sache. Un quota remis à zéro ou à une valeur par défaut au renouvellement le priverait de son réglage sans prévenir, et le lissage briderait aussitôt.

Le tableau de bord porte le bloc :

"reconduction": {
  "tacite": true,
  "quota_reconduit_gb": 50,
  "dernier_changement_le": "2026-08-09T12:00:00Z",
  "derniere_reconduction_le": "2026-09-09T07:00:00Z",
  "rappel_a_j_moins": 3
}

Les deux rappels

Quand Canal Message
J-3 avant la fin du cycle push + courriel « Votre cycle se termine le 9. Si votre forfait a changé, mettez-le à jour — sans quoi il repartira à l’identique. »
au renouvellement push + courriel « Nouveau cycle démarré, forfait reconduit à 50 Go. »

Le courriel rappelle en clair ce que nous ne pouvons pas faire : « votre abonnement Starlink reste le vôtre, nous ne pouvons ni le lire ni le modifier — nous étalons votre consommation pour éviter la panne sèche ». L’application devrait dire la même chose au même endroit.

5. Comment le lissage décide

Toutes les heures, pour chaque forfait actif :

reste          = quota total − consommé sur le cycle
allocation/jour = reste ÷ jours restants

Trois garde-fous que l’application doit connaître pour ne pas s’étonner :

  1. Le lissage ne dépasse jamais le plafond du gérant. Il ne fait que restreindre.
  2. Hystérésis de 20 % : un débit qui bouge de trois pourcents ne provoque aucune réécriture. Le routeur n’est pas retouché toutes les heures.
  3. Le lissage n’écrit rien directement sur le routeur : il pose un plafond effectif que le moteur de bridage applique à sa réconciliation, donc jusqu’à 5 minutes après le calcul. Un écran qui attend un effet immédiat affichera un décalage normal.

Au renouvellement, les recharges du cycle écoulé ne se reportent pas et le plafond est relâché — sans quoi le client resterait bridé le premier jour d’un cycle tout neuf.

Journal (GET /bandwidth/activity) : QUOTA_SMOOTHED, QUOTA_RELEASED, QUOTA_CYCLE_RESET, QUOTA_TOPUP_DELIVERED.


6. Déclarer son forfait — POST /starlink/priority/{nasId}/upgrade

Le nom de la route est conservé pour l’application publiée. Ce n’est pas un achat : le gérant déclare le forfait Starlink sur lequel il se trouve.

{ "upgrade_gb": 50 }
{
  "bloc_gb": 50,
  "base_quota_gb": 50,
  "cout_socle_xof": 15000,
  "cout_bloc_xof": 9400,
  "cout_total_xof": 24400,
  "devise": "XOF",
  "facture_par": "STARLINK",
  "note": "Ce montant est celui que Starlink vous facture. J+SERVICES n’encaisse rien dessus."
}

Le socle (15 000 F) est mensuel et dû quel que soit le volume : c’est l’accès prioritaire lui-même. Le compter par achat gonflerait son coût et lui ferait croire son mois déficitaire. upgrade_gb: 0 est un forfait légitime — l’accès sans bloc de données.

Le quota, c’est le bloc, pas le total. Le socle n’apporte aucun gigaoctet ; l’ajouter au quota ferait distribuer des données qui n’existent pas.

GET /starlink/priority/grille

{
  "devise": "XOF",
  "facture_par": "STARLINK",
  "socle_xof": 15000,
  "socle_periodicite": "MENSUELLE",
  "forfaits": [{ "gb": 50, "socle_xof": 15000, "bloc_xof": 9400, "total_xof": 24400 }, "…"]
}
Forfait Socle Bloc Total mensuel
0 Go 15 000 F 0 F 15 000 F
50 Go 15 000 F 9 400 F 24 400 F
100 Go 15 000 F 18 800 F 33 800 F
500 Go 15 000 F 47 000 F 62 000 F
1 To 15 000 F 94 000 F 109 000 F

N’écrivez aucun montant en dur : cette route est la seule source.


6 bis. La rentabilité — GET /starlink/priority/{nasId}/rentabilite

Le second rôle. Une jauge de quota ne dit pas au gérant s’il gagne sa vie ; « 24 400 F de forfait, 61 000 F encaissés » le dit.

{
  "configure": true,
  "cycle": { "debut": "2026-08-09", "renouvellement": "2026-09-09", "jours_restants": 10 },
  "forfait": { "bloc_gb": 50, "cout_total_xof": 24400, "facture_par": "STARLINK" },
  "recettes": {
    "source": "VENTES_ENREGISTREES",
    "enregistrees_xof": 61000,
    "declarees_xof": null,
    "avertissement": "Seules les ventes passées par MikhmoAI sont comptées. Saisissez vos ventes en espèces pour un bilan juste."
  },
  "bilan": {
    "cout_xof": 24400,
    "recettes_xof": 61000,
    "benefice_xof": 36600,
    "marge_pct": 60,
    "rentable": true,
    "cout_par_go": 488,
    "recette_par_go": 1220
  }
}

Ce que ces chiffres valent

bilan à null — et pourquoi ce n’est pas zéro

Nous ne voyons que les ventes passées par MikhmoAI. Un gérant qui encaisse au comptoir, ou dont le hotspot est piloté depuis les scripts du routeur, apparaîtrait déficitaire alors qu’il gagne de l’argent — et ce chiffre-là serait cru sur parole. recettes_xof, benefice_xof et rentable valent donc null, jamais 0. Affichez recettes.avertissement et invitez à saisir.

Deux populations : RADIUS MikhmoAI et Mikhmon local

Certains gérants vendent via notre RADIUS — leurs ventes arrivent dans sales. D’autres font tourner Mikhmon en local sur le routeur : leurs ventes n’arrivent jamais chez nous. Zéro ligne n’est donc pas zéro franc.

Le champ recettes.connu tranche, et recettes.motif dit pourquoi :

connu motif Sens
true null nos registres ont vu les ventes du cycle
true AUCUNE_VENTE_CE_CYCLE ce routeur vend d’habitude par nous, mais rien ce mois-ci — vrai zéro
false VENTES_HORS_MIKHMOAI ce routeur ne nous a jamais envoyé de vente : Mikhmon local, ou comptoir
false REGISTRE_INDISPONIBLE lecture en erreur, ne rien conclure

La distinction se fait sur l’historique du routeur, pas sur le seul cycle : c’est le seul signal disponible sans interroger le routeur.

PUT /starlink/priority/{nasId}/recettes

{ "recettes_xof": 61000, "source": "MIKHMON_LOCAL" }

source vaut MIKHMON_LOCAL (lu par l’application sur le routeur), SAISIE_MANUELLE ou AUTRE. Le tracer évite qu’on se demande plus tard si un chiffre a été mesuré ou estimé.

Le montant est rattaché au cycle en cours. Celui du mois dernier ne resservira pas : il afficherait un bénéfice qui n’existe plus, et le gérant le croirait.

Les recettes déclarées remplacent les recettes enregistrées, elles ne s’y ajoutent pas : additionner deux registres compterait deux fois les ventes déjà passées par nous. Quand le gérant saisit un montant, source passe à DECLAREES_PAR_LE_GERANT — dites-le à l’écran, il doit savoir quel chiffre il regarde.

7. Ce qu’il reste à brancher

Le rattachement au hub de paiement (transaction_id, webhook, passage à PAYE) et l’écran d’exécution des recharges ne sont pas dans cette livraison. Tant qu’ils manquent, une commande reste en EN_ATTENTE_PAIEMENT : c’est volontaire, et c’est visible.