# project.tier.write — Commander un palier, payé d'avance ; dryRun en rend l'aperçu.

> Engendrée depuis le registre. Ne pas modifier à la main.

EX-FACT-090 amendée (M-851, TR-420) : un palier se paie d'avance, au mois. Une montée prend effet tout de suite contre le paiement de la différence jusqu'à l'échéance, prélevé sur la carte enregistrée ; elle exige waiveWithdrawal (la version du texte rendu dans withdrawalWaiver), et le palier n'est posé qu'une fois le paiement accepté (order.state). Une descente prend effet à l'échéance, sans remboursement ; revenir au palier courant l'annule. Refusé pour un palier épuisé (palier_epuise), un palier dont l'isolation n'est pas servie, et une montée qui dépasserait le plafond de dépense. Souverain se chiffre sur devis.

| Propriété | Valeur |
| --- | --- |
| Niveau | `project` |
| Rang minimal | `admin` |
| Rôle transversal | `billing` |
| Portées | `billing:write` |
| Compte de service | ne la mène pas : elle est réservée à une personne |
| Réversibilité | I — irréversible : confirmation en deux temps, double validation humaine, vingt-quatre heures de grâce avant l'effet définitif |
| Idempotence | une clé d'idempotence est acceptée : le rejeu rend la première réponse |
| Limites de débit, par jeton | 600 appels par 60 secondes (`general`), et 60 appels par 60 secondes (`sensitive`) |

## API

`POST /v1/projects/{projectId}/tier` — succès en `200`.

| Paramètre | Où | Type | Requis | Description |
| --- | --- | --- | --- | --- |
| `projectId` | chemin | texte, motif `^prj_[0-9A-HJKMNP-TV-Z]{26}$` | oui | Identifiant de projet |
| `Idempotency-Key` | en-tête | texte, de 8 à 128 caractères | non | Clé d'idempotence. Un rejeu portant la même clé retourne le résultat initial sans nouvel effet de bord, et la réponse porte alors « Idempotency-Replayed: true ». Équivaut au champ « idempotencyKey » du corps ; les deux à la fois ne sont acceptés que s'ils sont identiques. |
| `tier` | corps | l'un de `discovery`, `standard`, `pro`, `sovereign` | oui |  |
| `dryRun` | corps | booléen | non | Vrai : rend l'aperçu sans rien changer. |
| `waiveWithdrawal` | corps | texte, 20 caractères au plus | non | La version du texte de renonciation que la personne a cochée (withdrawalWaiver.version de l'aperçu). Exigée pour une montée : elle demande l'exécution immédiate et renonce au droit de rétractation. |
| `idempotencyKey` | corps | texte, de 8 à 128 caractères | non | Clé d'idempotence, obligatoire. Un rejeu portant la même clé rend le résultat initial, sans nouvel effet de bord. |

La réponse :

| Champ | Type | Description |
| --- | --- | --- |
| `projectId` | texte, motif `^prj_[0-9A-HJKMNP-TV-Z]{26}$` | Identifiant de projet |
| `from` | l'un de `discovery`, `standard`, `pro`, `sovereign` |  |
| `to` | l'un de `discovery`, `standard`, `pro`, `sovereign` |  |
| `applied` | booléen | Faux pour un aperçu (dryRun) : rien n'a changé. |
| `currentSince` | texte, motif `^\d{4}-\d{2}-\d{2}$` | Depuis quand le projet est à son palier actuel. |
| `effectiveOn` | texte, motif `^\d{4}-\d{2}-\d{2}$` | Le jour d'effet, facturé au nouveau palier. |
| `cycle` | objet (`start`, `end`) |  |
| `remainingDays` | entier |  |
| `cycleDays` | entier |  |
| `monthlyPriceFromCents` | entier, ou nul |  |
| `monthlyPriceToCents` | entier, ou nul |  |
| `adjustmentCents` | entier, ou nul | Ce que la facture du mois prend en plus (ou en moins), HT, remise comprise. |
| `estimateBeforeCents` | entier, ou nul |  |
| `estimateAfterCents` | entier, ou nul |  |
| `spendCapCents` | entier |  |
| `isolationServed` | booléen | Faux : la plateforme ne sert pas l'isolation de ce palier (Souverain, sur devis) ; le changement est refusé. |
| `stock` | objet (`discovery`, `standard`, `pro`, `sovereign`) | Combien de projets de chaque palier la plateforme peut encore servir, calculé à la lecture. |
| `timing` | l'un de `immediate`, `at_renewal`, ou nul | « immediate » : une montée, servie dès le paiement ; « at_renewal » : une descente, à l'échéance. Nul pour Souverain (sur devis). |
| `renewalOn` | texte, motif `^\d{4}-\d{2}-\d{2}$` | L'échéance : le premier jour du mois suivant, fin de la période payée. |
| `dueNowCents` | entier, ou nul | Ce qui se paie avant d'être servi, HT, remise comprise : la différence jusqu'à l'échéance. |
| `withdrawalWaiver` | objet (`version`, `text`) | Le texte à cocher pour une montée, et sa version à renvoyer dans waiveWithdrawal. |
| `order` | objet (`id`, `state`, `amountCents`, `amountInclTaxCents`, `paidFrom`, `paidUntil`), ou nul | La commande passée : posée (applied), en attente de son paiement (awaiting_payment), descente planifiée à l'échéance (scheduled) ou descente annulée. Nulle pour un aperçu. |
| `undoToken` | nul | Aucun jeton pour cet appel, et null est la vérité : cette opération ne se défait pas par action.undo. |

En-têtes de la réponse : `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `Idempotency-Replayed`.

Autres réponses : `400`, `401`, `403`, `404`, `409`, `429`, `500`, `503` — communes à toute opération.

## Ligne de commande

`bastide project tier write --project-id <valeur> --tier <valeur>`

| Option | Type | Requise | Défaut | Description |
| --- | --- | --- | --- | --- |
| `--project-id` | texte | oui | — | Identifiant de projet |
| `--tier` | texte | oui | — |  |
| `--dry-run` | booléen | non | `false` | Vrai : rend l'aperçu sans rien changer. |
| `--waive-withdrawal` | texte | non | — | La version du texte de renonciation que la personne a cochée (withdrawalWaiver.version de l'aperçu). Exigée pour une montée : elle demande l'exécution immédiate et renonce au droit de rétractation. |
| `--idempotency-key` | texte | non | — | Clé d'idempotence, obligatoire. Un rejeu portant la même clé rend le résultat initial, sans nouvel effet de bord. |

La commande demande une confirmation avant d'agir, sauf avec « --yes ».

## MCP

Aucun outil : le cahier des charges exige qu'une personne la donne : un agent ne valide pas à sa place.

## Erreurs

| Code | Surface | Cause |
| --- | --- | --- |
| `isolation_non_servie` | les deux | Le palier demandé vend une isolation que la plateforme ne sert pas encore : une instance dédiée au palier Pro, un nœud dédié au palier Souverain. |
| `palier_epuise` | les deux | Le palier demandé n'a plus de place : la plateforme ne peut pas servir un projet de plus à ce palier pour l'instant. Rien n'a été changé ni facturé. |
| `plafond_de_depense_atteint` | les deux | Le plafond de dépense de l'organisation est atteint pour ce cycle : plus de construction ni de ressource nouvelle. Les sites déjà en ligne continuent d'être servis. |
| `renonciation_requise` | les deux | Un palier se paie d'avance et sert tout de suite : la demande d'exécution immédiate, qui vaut renonciation au droit de rétractation, n'a pas été cochée. |
| `encaissement_indisponible` | les deux | Le paiement n'est pas ouvert pour cette organisation : aucun palier payant ne se commande pour l'instant. |
| `moyen_de_paiement_requis` | les deux | Aucune carte enregistrée pour l'organisation : un palier se paie d'avance, sur la carte. |
| `coordonnees_de_facturation_requises` | les deux | Les coordonnées de facturation de l'organisation sont incomplètes : la commande ne peut pas être facturée. |
| `commande_en_cours` | les deux | Une commande de palier attend déjà son paiement pour ce projet. |
| `paiement_refuse` | les deux | Le paiement de la commande a été refusé ou n'a pas pu être créé : le palier n'a pas changé. |
| `idempotency_key_conflict` | API | Deux clés d'idempotence différentes ont été présentées, dans le corps et dans l'en-tête. |

S'y ajoutent les refus communs à toute opération — entrée invalide, authentification, autorisation, cible introuvable, conflit, limite de débit, indisponibilité — que le catalogue des erreurs décrit.

## Exemples

Voir ce que coûte le passage à Standard, sans rien changer :

```json
{
  "projectId": "prj_01JQ8ZK4M7NP2RSTVWXYZ34567",
  "tier": "standard",
  "dryRun": true,
  "idempotencyKey": "palier-lumeris-standard"
}
```
