---
title: "Sprint +23 Phase 3 — Stripe Customer Portal owner-only."
method: POST
path: "/api/v1/billing/portal-session"
tags: ["Facturation & Abonnements"]
---

# Sprint +23 Phase 3 — Stripe Customer Portal owner-only.

`POST /api/v1/billing/portal-session`

## Request

- Base URL: `https://erp.eyeot.fr/api/v1`
- URL: `https://erp.eyeot.fr/api/v1/api/v1/billing/portal-session`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Sprint +23 Phase 3 — Stripe Customer Portal owner-only.

    Crée une session Stripe Customer Portal pour permettre au PROPRIÉTAIRE
    de l'organisation de mettre à jour sa carte bleue, son adresse de
    facturation, télécharger ses factures et gérer son plan en self-service.

    Body : `{ return_url?: string }` — défaut `FRONTEND_URL/settings/billing`.

    Réponse : `{ url: "https://billing.stripe.com/p/session/..." }` (URL
    signée Stripe, lifetime ~5 minutes).

    Sécurité :
    - JWT requis
    - Owner-only : seul `Organization.owner_user_id == g.actor_user_id` peut
      ouvrir le portail (super_admin platform autorisé en bypass pour support)
    - Audit log `billing_portal_session_created` persisté
    - Step-up MFA NON requis : Stripe Portal a sa propre auth + le user ne
      voit que les données de son org. La modification de CB côté Stripe est
      en zone sécurisée hébergée Stripe (PCI-DSS scope = Stripe, pas eyeot).

    Cas particulier : si l'org est en trial sans `stripe_customer_id`, on
    crée le Customer Stripe à la volée et on persiste la FK avant d'ouvrir
    la session (idempotent en cas de retry).

## Request body

- PortalSessionRequest
  - `return_url` string, url, nullable

## Response `200`

Succès — payload `{data, meta}` (cf. API-GUIDE §3.1)

## Other responses

- `201`: Ressource créée. ID dans `data.id`.
- `400`: Validation Marshmallow échouée. `errors` détaille chaque champ invalide.
- `401`: JWT manquant, expiré ou révoqué.
- `404`: Ressource introuvable (ou cross-tenant — équivalent à hidden).
- `409`: État incohérent (email dupliqué, ressource déjà dans cet état, etc.).
- `422`: Validation Marshmallow échouée. `errors` détaille chaque champ invalide.
- `429`: Trop de requêtes. Honorer le header `Retry-After` (secondes).

---

[API](https://skmtc.dev/eyeot/apis/eyeot-erp-api.md) · [All operations](https://skmtc.dev/eyeot/apis/eyeot-erp-api/llms.txt) · [OpenAPI document](https://skmtc.dev/eyeot/apis/eyeot-erp-api/revisions/42ab95222a89?raw)
