Aller au contenu

Plafonner la dépense d'un client

Une équipe consomme des modèles à vos frais, et vous voulez borner ce qu’elle peut dépenser dans le mois. Cette page décrit le plan, son plafond, et ce que reçoit exactement l’appelant qui le dépasse.

Trois états, et deux refus qu’on ne doit pas confondre

Section intitulée « Trois états, et deux refus qu’on ne doit pas confondre »

Un plan est l’arrangement de facturation sous lequel la passerelle revend l’accès. plans.max_cost_euro est le plafond mensuel du client, en euros.

État du compte Ce que répond la passerelle
users.plan_id vide — aucun plan 403 {"error": "No plan assigned. Ask your administrator to assign one."}
plan dont le plafond est vide servi, quelle que soit la dépense — le plan est illimité, à dessein
plan avec un plafond servi tant que la dépense du mois est sous le plafond, 402 {"error": "Plan limit reached"} au-delà

Les deux codes sont différents parce que ce ne sont pas les mêmes personnes qui les lèvent.

402 dit « le titulaire a dépensé son plan » : un paiement, ou le mois suivant, le lève, et c’est le titulaire du compte qui agit.

403 dit « il n’y a pas de plan à dépenser » : aucun paiement ne peut y être imputé, le mois suivant n’y change rien, et seul l’exploitant le lève, en affectant un plan depuis la console. Un compte sans plan était auparavant servi sans limite ; ce n’est plus le cas depuis le 2026-07-29.

Un troisième 403 existe, de sens différent — {"error": "Endpoint \"<nom>\" is disabled."} — quand l’endpoint visé existe mais a été désactivé. Il est rendu avant le contrôle de quota : un endpoint éteint ne consomme rien et n’a pas à dépendre de la solvabilité de l’appelant.

Onglet Plans, formulaire Add plan : un nom, et un plafond mensuel en euros. Le nom est unique — la console refuse en 409 un nom déjà pris.

Laisser le champ de plafond vide crée un plan illimité. C’est une valeur distincte de zéro, et une situation distincte de « ce compte n’a pas de plan » : le plan illimité passe toujours, le compte sans plan ne passe jamais. Les deux donnaient jadis la même chose en base ; ils sont séparés depuis la migration 011-plans-plafond-illimite.

Le plan administrator, semé par cette même migration, est illimité par construction. La passerelle l’affecte au compte admin au démarrage si ce compte n’a pas déjà un plan : un plan que vous lui auriez donné délibérément, plafonné par exemple, n’est jamais repris par un redémarrage.

Onglet Users, sélecteur de la colonne Plan sur la ligne du compte. Le changement prend effet à la requête suivante.

La valeur No plan du même sélecteur retire son plan au compte : c’est le geste de blocage immédiat. Un badge « Blocked » apparaît sur la ligne.

Onglet Plans, édition en ligne. La modification porte sur le plan, donc sur tous les comptes qui le portent.

La suppression d’un plan est refusée en 409 tant que des comptes le portent — le message demande de les réaffecter d’abord. C’est délibéré : supprimer un plan porté bloquerait en masse les comptes qui en dépendent.

Le plafond n’est pas une estimation périodique : la dépense du mois est recalculée à chaque requête relayée, par agrégat sur request_logs joint aux prix par token de la table models.

  • La fenêtre est le mois calendaire en cours : timestamp >= date_trunc('month', NOW()). Elle repart à zéro au premier du mois, pas trente jours après la première requête.
  • La comparaison est >= : à plafond exactement atteint, le refus commence.
  • Le prix vient des modèles, en nano-euros par token entrant et par token sortant. Ce sont ces colonnes, et elles seules, qui alimentent le coût.

C’est la requête la plus coûteuse du service. Elle n’est pas exécutée pour un compte sans plan — il n’y a pas de plafond à comparer, donc pas de total à calculer.

À vérifier avant de promettre un plafond à un client. Chaque point ci-dessous fait qu’une consommation réelle ne compte pas dans le total.

Un modèle sans prix compte pour zéro. Les prix par token valent 0 par défaut à la création d’un modèle. Un modèle servi et laissé à zéro ne fait jamais monter aucun compteur, donc ne déclenche jamais aucun plafond. C’est le défaut le plus courant, et le plus silencieux : renseignez les prix en même temps que le modèle.

Un flux en cours n’est pas encore compté. Sur une réponse en streaming, la ligne de request_logs n’est écrite qu’à la fin du flux. Entre-temps, la consommation existe et n’est pas visible du contrôle de quota.

Un flux OpenAI sans stream_options compte pour zéro. Le format streaming d’OpenAI n’émet les compteurs de tokens que si le client a demandé stream_options: {include_usage: true}. Sans cela, la requête est journalisée à zéro token et ne pèse sur aucun plafond.

Une journalisation qui échoue est perdue sans bruit. Flux tronqué, amont qui coupe, base indisponible : la ligne est abandonnée, sans exception ni interruption de la réponse au client. Le choix est assumé — un échec de comptage ne coûte jamais rien à l’appelant — et sa contrepartie est qu’une consommation peut n’être jamais facturée.

La requête qui franchit le plafond est servie en entier. Le contrôle compare la dépense déjà enregistrée avant de relayer ; il ne devine pas le coût de la requête en cours. Le dépassement se constate donc toujours après coup, d’au moins un appel.

Les plafonds saisis avant la migration 007 ont pu être arrondis. La colonne était un entier : 12,50 € était enregistré 13 €, sans erreur. La migration a corrigé le type mais ne restaure pas les décimales perdues. Sur une base ancienne, relisez les plafonds à centimes et ressaisissez-les.

L’onglet Usage donne les coûts, filtrables par compte et par modèle. L’onglet Plans donne les plafonds.

Aucun écran ne rapproche les deux. Ni la liste des comptes, ni l’écran Usage, ni l’écran Plans ne montrent la consommation du mois en cours face au plafond qui bloquera : le rapprochement est à faire à la main, et l’approche d’un 402 ne se voit pas venir dans la console.

Le journal de la passerelle, lui, imprime le rapprochement à chaque requête relayée, sous la forme [COST] <compte>: €<dépensé> used / €<plafond> limit. C’est aujourd’hui le seul endroit où les deux nombres apparaissent côte à côte.