Les deux routes qui font le lien entre un compte Atlas et Stripe : ouvrir une session de paiement pour un palier payant, ou obtenir un lien vers le Customer Portal pour gérer un abonnement déjà actif.
POST /api/billing/checkout exige une session compte (cookie posé par /compte/) accompagnée d'un jeton CSRF — impossible à obtenir depuis un client API externe, seulement depuis le front Atlas connecté. POST /api/billing/portal n'exige ni session ni clé : elle s'authentifie par preuve de possession de l'email (un lien magique envoyé par courriel), ce qui la rend en pratique appelable depuis n'importe quel client — mais elle existe avant tout pour le flux /compte/, pas comme point d'entrée d'intégration. La seule route de facturation réellement pensée pour une clé API est GET /api/account/key-tier, documentée sur la page « Compte & clés API ».
/api/billing/checkoutCrée une Checkout Session Stripe pour un palier et un cycle de facturation donnés, et renvoie son URL. Requiert une session compte depuis le 18/08/2026 — l'email facturé vient de la session, jamais du corps de la requête, pour empêcher de payer au nom d'un tiers.
X-Atlas-CSRF obligatoires (require_session/require_csrf). Seule la présence de cet en-tête est vérifiée, jamais sa valeur. Sans compte connecté côté front, cette route renvoie 401/403 avant même de valider le corps de la requête.starter, pro, business).monthly).true pour que la session Stripe soit créée.cgv_accepted est false — « l'acceptation des CGV est obligatoire pour toute commande ».tier == "education" et l'email de la session n'est pas reconnu comme email éducation.atlas_session absent ou expiré).X-Atlas-CSRF absent — seule son absence déclenche l'erreur, sa valeur n'est jamais vérifiée.curl -X POST "https://atlasfeed.fr/api/billing/checkout" \
-H "Content-Type: application/json" \
-H "Cookie: atlas_session=..." \
-H "X-Atlas-CSRF: 1" \
-d '{"tier": "pro", "cycle": "monthly", "cgv_accepted": true, "lang": "fr"}'
import requests
# session + jeton CSRF obtenus au préalable via le flux /compte/,
# pas construits par un script indépendant.
resp = requests.post(
"https://atlasfeed.fr/api/billing/checkout",
cookies={"atlas_session": "..."},
headers={"X-Atlas-CSRF": "1"},
json={"tier": "pro", "cycle": "monthly", "cgv_accepted": True, "lang": "fr"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://atlasfeed.fr/api/billing/checkout", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-Atlas-CSRF": "1",
},
body: JSON.stringify({ tier: "pro", cycle: "monthly", cgv_accepted: true, lang: "fr" }),
});
const data = await resp.json();
{"url": "https://checkout.stripe.com/c/pay/..."}
/api/billing/portalEnvoie un lien de connexion signé à usage unique (valable 15 minutes) vers le Customer Portal Stripe, si l'email correspond à un client existant. La réponse est volontairement identique que l'email corresponde ou non, pour empêcher un tiers de vérifier par tâtonnement quels emails sont clients d'Atlas.
curl -X POST "https://atlasfeed.fr/api/billing/portal" \
-H "Content-Type: application/json" \
-d '{"email": "client@exemple.fr"}'
import requests
resp = requests.post(
"https://atlasfeed.fr/api/billing/portal",
json={"email": "client@exemple.fr"},
)
print(resp.status_code, resp.json())
const resp = await fetch("https://atlasfeed.fr/api/billing/portal", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "client@exemple.fr" }),
});
const data = await resp.json();
{"message": "Si cet email correspond à un compte Atlas, un lien de connexion vient de lui être envoyé (valable 15 minutes, à usage unique)."}
Deux routes du même groupe existent mais ne sont pas des points d'entrée pour un client API — elles ne sont mentionnées ici que pour mémoire.
GET /api/billing/portal/{token} — consomme le jeton du lien magique envoyé par POST /api/billing/portal et redirige (302) vers le Customer Portal Stripe ; 404 générique dans tous les cas d'échec. Une redirection à usage unique, pas une ressource à interroger.POST /api/billing/webhook — réception des événements Stripe (paiement complété), authentifiée par signature stripe-signature et consommée uniquement par les serveurs Stripe. Un webhook serveur-à-serveur, sans pertinence pour un client de l'API.GET /api/account/key-tier — voir la page « Compte & clés API ».