Documentation J+SERVICES Guides Référence API

Contrat API — Le 409 DEPLOYMENT_IN_PROGRESS n’est pas un échec

Réponse du backend à la note de l’équipe app mobile du 20 août 2026 sur POST /api/v1/radius/nas. Complète CONTRAT-API-RADIUS-ENROLEMENT-NAS, qui reste le contrat de référence de l’enrôlement.

Votre analyse est juste sur le fond, et refuser un contournement côté front était le bon réflexe. Mais le 409 que vous observez n’est pas une tentative antérieure : c’est votre propre requête, trois secondes plus tôt.


D’abord : ce n’est pas votre app qui injecte le script

Votre note décrit « la séquence de provisionnement (injection du script MikroTik, etc.) » comme une opération de l’application. En production, c’est le backend qui l’exécute, en ouvrant une session SSH/API native vers le routeur — et c’est ce qui rend l’appel long, donc c’est la cause première de votre 409.

L’appel a deux modes, décidés par le champ apply_mode :

apply_mode Qui injecte Durée de l’appel 409 possible ?
"app_apply" Vous, en contact avec le routeur immédiate non
absent (défaut) Le backend, en SSH/API native 10 à 13 s oui

Vos appels n’envoient pas apply_mode. Ils tombent donc dans le mode backend, où le backend pousse le script lui-même — pendant que votre app récupère ce même script sur GET /nas/{id}/provisioning et l’applique de son côté. La configuration est injectée deux fois, et vous payez l’attente d’un travail que vous refaites.

En app_apply, aucun verrou n’est posé et aucune session SSH n’est ouverte : la réponse est immédiate et le 409 DEPLOYMENT_IN_PROGRESS ne peut structurellement pas survenir.

C’est une omission de notre côté. apply_mode n’était documenté que dans deux notes internes, non publiées sur ce portail — le contrat que vous pouviez lire disait « router_serial suffit ». C’est corrigé : voir la section apply_mode du contrat d’enrôlement.

Tout ce qui suit décrit le mode backend, qui reste valide et supporté — et vers lequel vous retomberez si vous décidez de laisser le backend écrire.


Verdict sur les deux options proposées

Option 1 — retenue, livrée, en production

Le 409 porte désormais idempotent: true et l’objet nas à la racine, exactement comme la réponse de succès. RadiusBackendService.ts:336 devrait le consommer tel quel.

Option 2 — écartée

« Ré-initialiser le timestamp de provisioning » relancerait un déploiement déjà en vol : deux push SSH concurrents sur le même routeur, et si le second échoue, son rollback supprime le NAS que le premier vient de créer. Le verrou existe précisément pour ça, et il reste.

Il est borné à 180 s : un processus mort n’enferme jamais un client hors de son routeur.


Ce que le 409 raconte vraiment

L’enrôlement est synchrone et dure 10 à 13 secondes : le backend synchronise les comptes hotspot (7 792 users et 18 profils sur notre routeur de test), puis pousse le script, et ne répond qu’ensuite. Le client coupe bien avant.

Séquence relevée en production le 20 août 2026 :

05:41:37   POST /radius/nas         le backend démarre le déploiement et pose le verrou.
                                    L'app coupe la connexion : aucun statut n'est journalisé.

05:41:40   POST /radius/nas → 409   (+3 s) le retry se heurte au verrou posé par la requête
                                    d'il y a trois secondes. L'app affiche un échec.

05:41:47   Déploiement réussi       (+10 s) le PREMIER appel est allé au bout. Le NAS existe,
                                    le script est sur le routeur.

La même séquence s’est rejouée à 02:47 le même jour : 409 à 02:47:55, déploiement réussi à 02:48:08. Dans les deux cas, le backend a fait le travail pendant que l’utilisateur regardait un écran d’erreur.

Ce n’est pas l’infrastructure qui coupe. live.jmoai.net est configuré en proxy_read_timeout 600s. Le timeout est côté application.


Le 409 n’est pas votre principal problème

Sur 24 heures, POST /api/v1/radius/nas a été appelé 21 fois. Le 409 en représente 2.

HTTP Nb Code Cause réelle
502 11 RADIUS_PROVISION_ERROR Routeur injoignable — rollback du NAS
503 2 RADIUS_BRIDGE_UNAVAILABLE Même routeur, bridge en timeout
400 3 ROUTER_SERIAL_REQUIRED Numéro de série absent de l’appel
409 2 DEPLOYMENT_IN_PROGRESS Le sujet de cette note
2 (aucun) Requête avortée par le client
200 1 Enrôlement abouti

Les 13 lignes 502/503 sont un seul et même routeur client, dont le tunnel est mort : SSH, REST 8081, 80 et 443 tous injoignables, disjoncteur ouvert. Le backend refuse correctement et annule le NAS.

Ce client ne peut pas s’enrôler tant que son matériel ne répond pas, et aucun changement de contrat n’y changera quoi que ce soit. Si vos utilisateurs remontent « l’enrôlement ne marche pas », c’est très majoritairement ce cas-là qu’ils décrivent — pas le 409.

Les deux 409 et les deux requêtes avortées viennent tous du même routeur de test, le nôtre.


Ce que renvoie le backend depuis le 20 août 2026, 06:14

idempotent et nas sont à la racine, pas sous details :

HTTP/1.1 409 Conflict
Retry-After: 15
{
  "success": false,
  "code": "DEPLOYMENT_IN_PROGRESS",
  "error": {
    "code": "DEPLOYMENT_IN_PROGRESS",
    "message": "Le script RADIUS est en cours d’application sur le routeur. Patientez avant de réessayer."
  },
  "idempotent": true,
  "nas": {
    "id": "…",
    "nasname": "10.255.0.15",
    "shortname": "…",
    "status": "…",
    "provisioning": {
      "required": true,
      "endpoint": "/api/v1/radius/nas/{id}/provisioning",
      "verify_endpoint": "/api/v1/radius/nas/{id}/verify"
    }
  },
  "details": {
    "nas_id": "…",
    "started_at": "2026-08-20T05:41:37.000Z",
    "retry_after_seconds": 15,
    "deployment": { "status": "DEPLOYING", "started_at": "…" }
  }
}

Deux points à noter.

success: false manquait sur toutes les réponses d’erreur de cette route. Un client qui testait success === false lisait undefined — donc un succès. C’est corrigé, et ça vaut aussi pour les 400, 502 et 503 du tableau ci-dessus.

Le secret RADIUS n’est pas dans ce corps, et n’y sera pas : un corps d’erreur finit dans les journaux du terminal.


Ce qui reste côté application

A. Choisir votre mode, et vous y tenir

Si votre app applique le script — ce que votre note décrit — envoyez apply_mode: "app_apply". L’appel devient immédiat, le verrou n’existe plus, le 409 disparaît, et vous cessez d’injecter deux fois la même configuration. C’est le correctif à un seul champ.

Si vous préférez laisser le backend écrire, alors retirez l’injection côté app et remontez le timeout à 60 s sur cet appel. Sans ça, la première requête sera toujours coupée à 3 s : vous entrerez systématiquement dans le chemin dégradé au lieu de recevoir le 201 qui vous revient.

B. Sur 409, ne jamais rejouer POST /nas

Attendre retry_after_seconds, puis appeler POST /nas/{id}/verify avec le nas.id reçu. Le NAS de la réponse est le vôtre, déjà créé. Le déploiement aboutira ou sera annulé par le backend — jamais laissé à moitié.

C. Distinguer 502/503 du 409 à l’écran

Un 502 RADIUS_PROVISION_ERROR est un état du matériel, pas une panne de l’application : « Votre routeur ne répond pas. Vérifiez qu’il est allumé et connecté. » Le 409, lui, ne devrait plus jamais produire d’écran d’erreur.


Voir aussi