openapi: 3.0.3 info: title: 'ASSOVIX API' description: "API REST multi-tenant pour la gestion d'associations africaines (tontines, ONG, mutuelles, syndicats).\n\n## Identification du tenant\n\nToutes les routes `/api/v1/*` (sauf `register`, `login` et `login/select-tenant`) nécessitent le header **`X-Tenant-Slug`** :\n\n```\nX-Tenant-Slug: mon-association\n```\n\nLe slug est l'identifiant unique de l'association, choisi lors de l'inscription.\n\n## Format de réponse uniforme\n\nToutes les réponses suivent ce format :\n\n```json\n{\n \"success\": true,\n \"message\": \"Description lisible.\",\n \"data\": {},\n \"meta\": { \"timestamp\": \"2026-04-27T10:00:00+00:00\", \"version\": \"v1\" }\n}\n```\n\nEn cas d'erreur, le champ `errors` s'ajoute et `data` vaut `null`.\n\n## Codes d'erreur\n\n| Code | Signification |\n|------|---------------|\n| 200 | Succès |\n| 201 | Ressource créée |\n| 401 | Token manquant ou expiré |\n| 403 | Accès interdit (tenant suspendu) |\n| 404 | Ressource ou tenant introuvable |\n| 422 | Validation échouée |\n| 429 | Trop de requêtes (100/min API · 10/min auth) |\n| 500 | Erreur serveur interne |" version: 1.0.0 servers: - url: 'https://api-assovix.gestiem.com' tags: - name: Authentication description: '' - name: 'Tenant (Admin)' description: '' - name: 'Member Groups' description: '' - name: Members description: '' components: securitySchemes: default: type: http scheme: bearer description: 'Obtenez votre token via **POST /api/v1/auth/login**, puis passez-le en header : `Authorization: Bearer {token}`' security: - default: [] paths: /api/v1/auth/logout: post: summary: 'Se déconnecter' operationId: seDconnecter description: "Révoque **tous** les tokens Sanctum de l'utilisateur connecté (toutes les sessions)." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 200: description: 'Déconnexion réussie' content: application/json: schema: type: object example: success: true message: 'Déconnexion réussie.' data: null meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Déconnexion réussie.' data: type: string example: null nullable: true meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Token manquant ou expiré' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - Authentication /api/v1/auth/me: get: summary: 'Profil et permissions' operationId: profilEtPermissions description: "Retourne le profil complet de l'utilisateur connecté et\nla liste de ses permissions dans le contexte du tenant courant." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 200: description: Succès content: application/json: schema: type: object example: success: true message: 'Utilisateur courant.' data: user: id: 550e8400-e29b-41d4-a716-446655440000 tenant_id: 660e8400-e29b-41d4-a716-446655440000 first_name: Kofi last_name: Mensah email: kofi@example.com phone: '+22997000000' avatar_url: null locale: fr is_active: true last_login_at: '2026-04-27T10:00:00+00:00' roles: - name: admin guard_name: web permissions: - manage-members - manage-cotisations - view-reports meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Utilisateur courant.' data: type: object properties: user: type: object properties: id: type: string example: 550e8400-e29b-41d4-a716-446655440000 tenant_id: type: string example: 660e8400-e29b-41d4-a716-446655440000 first_name: type: string example: Kofi last_name: type: string example: Mensah email: type: string example: kofi@example.com phone: type: string example: '+22997000000' avatar_url: type: string example: null nullable: true locale: type: string example: fr is_active: type: boolean example: true last_login_at: type: string example: '2026-04-27T10:00:00+00:00' roles: type: array example: - name: admin guard_name: web items: type: object properties: name: type: string example: admin guard_name: type: string example: web permissions: type: array example: - manage-members - manage-cotisations - view-reports items: type: string meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Token manquant ou expiré' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - Authentication /api/v1/auth/refresh: post: summary: 'Rafraîchir le token' operationId: rafrachirLeToken description: "Révoque le token courant et émet un nouveau token Sanctum valide 24h.\nUtile pour prolonger une session sans redemander les credentials." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 200: description: 'Token rafraîchi' content: application/json: schema: type: object example: success: true message: 'Token rafraîchi.' data: token: 3|assovix_newFreshToken789xyz meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Token rafraîchi.' data: type: object properties: token: type: string example: 3|assovix_newFreshToken789xyz meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Token manquant ou expiré' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - Authentication /api/v1/auth/register: post: summary: 'Créer un compte association' operationId: crerUnCompteAssociation description: "Crée simultanément un nouveau tenant (association) et son premier utilisateur\navec le rôle `admin`. Retourne un token Sanctum immédiatement utilisable.\nCe endpoint ne nécessite pas le header `X-Tenant-Slug` car le tenant n'existe pas encore." parameters: [] responses: 201: description: 'Inscription réussie' content: application/json: schema: type: object example: success: true message: 'Inscription réussie.' data: token: 1|assovix_K2c3QTLQgDywEmIa0qaXL1ivbosnIr4 user: id: 550e8400-e29b-41d4-a716-446655440000 tenant_id: 660e8400-e29b-41d4-a716-446655440000 first_name: Kofi last_name: Mensah email: kofi@example.com phone: '+22997000000' avatar_url: null locale: fr is_active: true last_login_at: null tenant: id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis' association_type: tontine slug: tontine-les-amis country: BJ currency: XOF plan: free status: trial trial_ends_at: '2026-05-11T10:00:00+00:00' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Inscription réussie.' data: type: object properties: token: type: string example: 1|assovix_K2c3QTLQgDywEmIa0qaXL1ivbosnIr4 user: type: object properties: id: type: string example: 550e8400-e29b-41d4-a716-446655440000 tenant_id: type: string example: 660e8400-e29b-41d4-a716-446655440000 first_name: type: string example: Kofi last_name: type: string example: Mensah email: type: string example: kofi@example.com phone: type: string example: '+22997000000' avatar_url: type: string example: null nullable: true locale: type: string example: fr is_active: type: boolean example: true last_login_at: type: string example: null nullable: true tenant: type: object properties: id: type: string example: 660e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Tontine Les Amis' association_type: type: string example: tontine slug: type: string example: tontine-les-amis country: type: string example: BJ currency: type: string example: XOF plan: type: string example: free status: type: string example: trial trial_ends_at: type: string example: '2026-05-11T10:00:00+00:00' meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 422: description: '' content: application/json: schema: oneOf: - description: 'Slug déjà utilisé' type: object example: success: false message: 'Données invalides.' data: null errors: slug: - 'The slug has already been taken.' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: slug: type: array example: - 'The slug has already been taken.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 - description: 'Email invalide' type: object example: success: false message: 'Données invalides.' data: null errors: email: - 'The email field must be a valid email address.' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: email: type: array example: - 'The email field must be a valid email address.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 - description: 'Mot de passe trop court' type: object example: success: false message: 'Données invalides.' data: null errors: password: - 'The password field must be at least 8 characters.' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: password: type: array example: - 'The password field must be at least 8 characters.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: association_name: type: string description: "Nom de l'association." example: 'Tontine Les Amis' association_type: type: string description: "Type d'association : tontine, mutuelle, ong, syndicat, club_culturel, association_sportive, communaute_religieuse." example: tontine slug: type: string description: 'Identifiant URL unique (minuscules, chiffres, tirets).' example: tontine-les-amis country: type: string description: 'Code pays ISO 3166-1 alpha-2.' example: BJ currency: type: string description: 'Devise : XOF, XAF, GNF, USD, EUR.' example: XOF first_name: type: string description: "Prénom de l'administrateur." example: Kofi last_name: type: string description: 'Nom de famille.' example: Mensah email: type: string description: 'Adresse email.' example: kofi@example.com phone: type: string description: 'Numéro de téléphone (optionnel).' example: '+22997000000' nullable: true password: type: string description: 'Mot de passe (min. 8 caractères).' example: Secret@2026! password_confirmation: type: string description: 'Confirmation du mot de passe.' example: Secret@2026! required: - association_name - association_type - slug - country - currency - first_name - last_name - email - password - password_confirmation security: [] /api/v1/auth/login: post: summary: 'Se connecter' operationId: seConnecter description: "Authentifie un utilisateur **sans header `X-Tenant-Slug`**. Le système\nidentifie automatiquement l'association en croisant `email` et `password`.\n- Cas standard (un seul match) : retourne un token Sanctum, l'utilisateur,\n le tenant et les permissions.\n- Cas ambigu (l'email existe dans plusieurs associations actives) : retourne\n un `login_intent` court-vie (5 min) et la liste des associations à choisir.\n Aucun token Sanctum n'est émis tant que la sélection n'est pas faite via\n `POST /api/v1/auth/login/select-tenant`." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Connexion directe (un seul tenant)' type: object example: success: true message: 'Connexion réussie.' data: token: 2|assovix_4xK2c3QTLQgDywEmIa0qaXL1ivbosnIr4 user: id: 550e8400-... email: kofi@example.com is_active: true tenant: id: 660e8400-... name: 'Tontine Les Amis' slug: tontine-les-amis permissions: - manage-members meta: timestamp: '2026-05-04T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Connexion réussie.' data: type: object properties: token: type: string example: 2|assovix_4xK2c3QTLQgDywEmIa0qaXL1ivbosnIr4 user: type: object properties: id: type: string example: 550e8400-... email: type: string example: kofi@example.com is_active: type: boolean example: true tenant: type: object properties: id: type: string example: 660e8400-... name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis permissions: type: array example: - manage-members items: type: string meta: type: object properties: timestamp: type: string example: '2026-05-04T10:00:00+00:00' version: type: string example: v1 - description: "Sélection d'association requise" type: object example: success: true message: 'Plusieurs associations correspondent. Sélectionnez-en une.' data: requires_tenant_selection: true login_intent: lit_9f3a2b81e4c64d9bb9a1d0e2f1c8b3a7 expires_at: '2026-05-04T10:05:00+00:00' associations: - id: 660e8400-... name: 'Tontine Les Amis' slug: tontine-les-amis association_type: tontine logo_url: null country: BJ meta: timestamp: '2026-05-04T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Plusieurs associations correspondent. Sélectionnez-en une.' data: type: object properties: requires_tenant_selection: type: boolean example: true login_intent: type: string example: lit_9f3a2b81e4c64d9bb9a1d0e2f1c8b3a7 expires_at: type: string example: '2026-05-04T10:05:00+00:00' associations: type: array example: - id: 660e8400-... name: 'Tontine Les Amis' slug: tontine-les-amis association_type: tontine logo_url: null country: BJ items: type: object properties: id: type: string example: 660e8400-... name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis association_type: type: string example: tontine logo_url: type: string example: null nullable: true country: type: string example: BJ meta: type: object properties: timestamp: type: string example: '2026-05-04T10:00:00+00:00' version: type: string example: v1 422: description: 'Identifiants incorrects' content: application/json: schema: type: object example: success: false message: 'Données invalides.' data: null errors: email: - 'Identifiants incorrects.' meta: timestamp: '2026-05-04T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: email: type: array example: - 'Identifiants incorrects.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-05-04T10:00:00+00:00' version: type: string example: v1 tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: "Adresse email de l'utilisateur." example: kofi@example.com password: type: string description: 'Mot de passe.' example: Secret@2026! required: - email - password security: [] /api/v1/auth/login/select-tenant: post: summary: 'Sélectionner une association après login multi-tenant' operationId: slectionnerUneAssociationAprsLoginMultiTenant description: "Consomme un `login_intent` émis par `POST /auth/login` quand l'email\nmatche plusieurs associations, et retourne un token Sanctum complet\npour le tenant choisi. Le `login_intent` est **one-shot** : un second\nappel échoue. TTL 5 min." parameters: [] responses: 200: description: 'Sélection réussie' content: application/json: schema: type: object example: success: true message: 'Connexion réussie.' data: token: 3|assovix_freshTokenForSelectedTenant user: id: 550e8400-... email: kofi@example.com tenant: id: 660e8400-... name: 'Tontine Les Amis' slug: tontine-les-amis permissions: - manage-members meta: timestamp: '2026-05-04T10:00:30+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Connexion réussie.' data: type: object properties: token: type: string example: 3|assovix_freshTokenForSelectedTenant user: type: object properties: id: type: string example: 550e8400-... email: type: string example: kofi@example.com tenant: type: object properties: id: type: string example: 660e8400-... name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis permissions: type: array example: - manage-members items: type: string meta: type: object properties: timestamp: type: string example: '2026-05-04T10:00:30+00:00' version: type: string example: v1 422: description: '' content: application/json: schema: oneOf: - description: 'Intent expiré ou inconnu' type: object example: success: false message: 'Données invalides.' data: null errors: login_intent: - 'Lien expiré, recommencez la connexion.' meta: timestamp: '2026-05-04T10:05:30+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: login_intent: type: array example: - 'Lien expiré, recommencez la connexion.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-05-04T10:05:30+00:00' version: type: string example: v1 - description: 'Tenant non autorisé pour cet intent' type: object example: success: false message: 'Données invalides.' data: null errors: tenant_id: - 'Sélection invalide.' meta: timestamp: '2026-05-04T10:00:30+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: tenant_id: type: array example: - 'Sélection invalide.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-05-04T10:00:30+00:00' version: type: string example: v1 tags: - Authentication requestBody: required: true content: application/json: schema: type: object properties: login_intent: type: string description: 'Identifiant retourné par /auth/login.' example: lit_9f3a2b81e4c64d9bb9a1d0e2f1c8b3a7 tenant_id: type: string description: "UUID de l'association choisie." example: 660e8400-e29b-41d4-a716-446655440000 required: - login_intent - tenant_id security: [] /api/admin/tenants: get: summary: 'Lister les tenants' operationId: listerLesTenants description: "Retourne la liste paginée de tous les tenants (25 par page), triée par date de création décroissante.\nFiltres disponibles : `status` et `search` (recherche dans `name` et `slug`)." parameters: - in: query name: status description: 'Filtre par statut. Valeurs : `active`, `suspended`, `trial`.' example: active required: false schema: type: string description: 'Filtre par statut. Valeurs : `active`, `suspended`, `trial`.' example: active - in: query name: search description: 'Recherche dans le nom ou le slug du tenant.' example: tontine required: false schema: type: string description: 'Recherche dans le nom ou le slug du tenant.' example: tontine - in: query name: page description: 'Numéro de page. Défaut: 1.' example: 1 required: false schema: type: integer description: 'Numéro de page. Défaut: 1.' example: 1 responses: 200: description: 'Liste paginée' content: application/json: schema: type: object example: success: true message: 'Liste des tenants.' data: - id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis' slug: tontine-les-amis country: BJ currency: XOF plan: free status: trial trial_ends_at: '2026-05-11T10:00:00+00:00' created_at: '2026-04-27T10:00:00+00:00' meta: current_page: 1 last_page: 3 per_page: 25 total: 72 timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Liste des tenants.' data: type: array example: - id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis' slug: tontine-les-amis country: BJ currency: XOF plan: free status: trial trial_ends_at: '2026-05-11T10:00:00+00:00' created_at: '2026-04-27T10:00:00+00:00' items: type: object properties: id: type: string example: 660e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis country: type: string example: BJ currency: type: string example: XOF plan: type: string example: free status: type: string example: trial trial_ends_at: type: string example: '2026-05-11T10:00:00+00:00' created_at: type: string example: '2026-04-27T10:00:00+00:00' meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 3 per_page: type: integer example: 25 total: type: integer example: 72 timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' post: summary: 'Créer un tenant' operationId: crerUnTenant description: "Crée manuellement un nouveau tenant sans créer d'utilisateur associé.\nRéservé au super admin de la plateforme." parameters: [] responses: 201: description: 'Tenant créé' content: application/json: schema: type: object example: success: true message: 'Tenant créé.' data: id: 770e8400-e29b-41d4-a716-446655440000 name: 'ONG Solidarité Bénin' slug: ong-solidarite-benin country: SN currency: XOF plan: free status: trial created_at: '2026-04-27T10:00:00+00:00' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Tenant créé.' data: type: object properties: id: type: string example: 770e8400-e29b-41d4-a716-446655440000 name: type: string example: 'ONG Solidarité Bénin' slug: type: string example: ong-solidarite-benin country: type: string example: SN currency: type: string example: XOF plan: type: string example: free status: type: string example: trial created_at: type: string example: '2026-04-27T10:00:00+00:00' meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 422: description: 'Slug dupliqué' content: application/json: schema: type: object example: success: false message: 'Données invalides.' data: null errors: slug: - 'The slug has already been taken.' meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Données invalides.' data: type: string example: null nullable: true errors: type: object properties: slug: type: array example: - 'The slug has already been taken.' items: type: string meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: "Nom de l'association." example: 'ONG Solidarité Bénin' slug: type: string description: 'Identifiant URL unique (minuscules, chiffres, tirets).' example: ong-solidarite-benin country: type: string description: 'Code pays ISO 3166-1 alpha-2.' example: SN nullable: true currency: type: string description: 'Devise : `XOF`, `XAF`, `GNF`, `USD`, `EUR`.' example: XOF nullable: true plan: type: string description: 'Plan : `free`, `starter`, `pro`, `enterprise`.' example: free nullable: true status: type: string description: 'Statut : `active`, `suspended`, `trial`.' example: trial nullable: true required: - name - slug '/api/admin/tenants/{id}': put: summary: 'Modifier un tenant' operationId: modifierUnTenant description: "Met à jour les informations d'un tenant existant.\nSeuls les champs envoyés sont modifiés (comportement PATCH sur PUT)." parameters: [] responses: 200: description: 'Tenant mis à jour' content: application/json: schema: type: object example: success: true message: 'Tenant mis à jour.' data: id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis v2' slug: tontine-les-amis country: CI currency: XOF plan: starter status: trial meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Tenant mis à jour.' data: type: object properties: id: type: string example: 660e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Tontine Les Amis v2' slug: type: string example: tontine-les-amis country: type: string example: CI currency: type: string example: XOF plan: type: string example: starter status: type: string example: trial meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 404: description: 'Tenant introuvable' content: application/json: schema: type: object example: success: false message: 'Ressource introuvable.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Ressource introuvable.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: "Nouveau nom de l'association." example: 'Tontine Les Amis v2' country: type: string description: 'Code pays ISO 3166-1 alpha-2.' example: CI currency: type: string description: 'Devise : `XOF`, `XAF`, `GNF`, `USD`, `EUR`.' example: XOF plan: type: string description: 'Plan : `free`, `starter`, `pro`, `enterprise`.' example: starter locale: type: string description: 'Locale BCP 47.' example: fr delete: summary: 'Supprimer un tenant' operationId: supprimerUnTenant description: "Soft-delete du tenant. Les données sont conservées en base (`deleted_at` non null).\nCette action est réversible via une restauration manuelle." parameters: [] responses: 200: description: 'Tenant supprimé' content: application/json: schema: type: object example: success: true message: 'Tenant supprimé.' data: null meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Tenant supprimé.' data: type: string example: null nullable: true meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 404: description: 'Tenant introuvable' content: application/json: schema: type: object example: success: false message: 'Ressource introuvable.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Ressource introuvable.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' parameters: - in: path name: id description: 'UUID du tenant.' example: 660e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/admin/tenants/{id}/suspend': put: summary: 'Suspendre un tenant' operationId: suspendreUnTenant description: "Passe le statut du tenant à `suspended`.\nLes utilisateurs du tenant recevront une erreur 403 sur chaque requête." parameters: [] responses: 200: description: 'Tenant suspendu' content: application/json: schema: type: object example: success: true message: 'Tenant suspendu.' data: id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis' slug: tontine-les-amis status: suspended meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Tenant suspendu.' data: type: object properties: id: type: string example: 660e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis status: type: string example: suspended meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 404: description: 'Tenant introuvable' content: application/json: schema: type: object example: success: false message: 'Ressource introuvable.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Ressource introuvable.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' parameters: - in: path name: id description: 'UUID du tenant.' example: 660e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/admin/tenants/{id}/activate': put: summary: 'Activer un tenant' operationId: activerUnTenant description: "Passe le statut du tenant à `active`.\nRétablit l'accès complet à l'API pour les utilisateurs du tenant." parameters: [] responses: 200: description: 'Tenant activé' content: application/json: schema: type: object example: success: true message: 'Tenant activé.' data: id: 660e8400-e29b-41d4-a716-446655440000 name: 'Tontine Les Amis' slug: tontine-les-amis status: active meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: true message: type: string example: 'Tenant activé.' data: type: object properties: id: type: string example: 660e8400-e29b-41d4-a716-446655440000 name: type: string example: 'Tontine Les Amis' slug: type: string example: tontine-les-amis status: type: string example: active meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 401: description: 'Non authentifié' content: application/json: schema: type: object example: success: false message: 'Non authentifié.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Non authentifié.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 404: description: 'Tenant introuvable' content: application/json: schema: type: object example: success: false message: 'Ressource introuvable.' data: null errors: [] meta: timestamp: '2026-04-27T10:00:00+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Ressource introuvable.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-04-27T10:00:00+00:00' version: type: string example: v1 tags: - 'Tenant (Admin)' parameters: - in: path name: id description: 'UUID du tenant.' example: 660e8400-e29b-41d4-a716-446655440000 required: true schema: type: string /api/v1/member-groups: get: summary: 'Liste des groupes' operationId: listeDesGroupes description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - 'Member Groups' post: summary: 'Créer un groupe' operationId: crerUnGroupe description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - 'Member Groups' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Nom du groupe.' example: Bureau description: type: string description: '' example: 'Rerum non doloribus aspernatur ad eius molestiae accusantium.' nullable: true color: type: string description: 'Couleur hex (ex: #059669).' example: '#059669' nullable: true parent_id: type: string description: 'UUID du groupe parent.' example: null nullable: true required: - name '/api/v1/member-groups/{id}': put: summary: 'Modifier un groupe' operationId: modifierUnGroupe description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - 'Member Groups' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: validation.max. example: vwgshl description: type: string description: '' example: 'Rerum non doloribus aspernatur ad eius molestiae accusantium.' nullable: true color: type: string description: 'Must match the regex /^#[0-9A-Fa-f]{6}$/.' example: '#057Fce' nullable: true parent_id: type: string description: 'validation.uuid The id of an existing record in the member_groups table. Must not be one of .' example: a00372d2-8e4a-326e-8cb5-f40c121e1465 nullable: true delete: summary: 'Supprimer un groupe' operationId: supprimerUnGroupe description: 'Refusé si le groupe contient des membres (actifs ou supprimés).' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - 'Member Groups' parameters: - in: path name: id description: 'UUID du groupe.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/v1/member-groups/{id}/members': get: summary: "Membres d'un groupe" operationId: membresDunGroupe description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - 'Member Groups' parameters: - in: path name: id description: 'UUID du groupe.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string /api/v1/members/stats: get: summary: 'Statistiques des membres' operationId: statistiquesDesMembres description: 'Retourne le total, la répartition par statut et par groupe, les nouveaux ce mois/année.' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members /api/v1/members/export: get: summary: 'Exporter les membres en CSV' operationId: exporterLesMembresEnCSV description: '' parameters: - in: query name: status description: 'Filtre statut.' example: active required: false schema: type: string description: 'Filtre statut.' example: active - in: query name: group_id description: 'Filtre groupe.' example: null required: false schema: type: string description: 'Filtre groupe.' example: null - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members /api/v1/members/template: get: summary: "Télécharger le template CSV d'import" operationId: tlchargerLeTemplateCSVDimport description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members /api/v1/members/import: post: summary: 'Importer des membres depuis CSV' operationId: importerDesMembresDepuisCSV description: "Importe des membres depuis un fichier CSV. Max 1000 lignes.\nColonnes requises : first_name*, last_name*. Toutes les autres sont optionnelles." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: 'Fichier CSV (max 5MB).' required: - file /api/v1/members: get: summary: 'Liste des membres' operationId: listeDesMembres description: "Retourne la liste paginée des membres du tenant courant.\nSupporte la recherche, le filtrage et le tri." parameters: - in: query name: search description: 'Recherche sur nom, prénom, email, numéro.' example: Kofi required: false schema: type: string description: 'Recherche sur nom, prénom, email, numéro.' example: Kofi - in: query name: status description: 'Filtre statut : active, inactive, pending, suspended, honorary, deceased.' example: active required: false schema: type: string description: 'Filtre statut : active, inactive, pending, suspended, honorary, deceased.' example: active - in: query name: group_id description: 'UUID du groupe.' example: null required: false schema: type: string description: 'UUID du groupe.' example: null - in: query name: sponsor_id description: 'UUID du parrain.' example: null required: false schema: type: string description: 'UUID du parrain.' example: null - in: query name: joined_from description: 'Date début adhésion (YYYY-MM-DD).' example: '2026-01-01' required: false schema: type: string description: 'Date début adhésion (YYYY-MM-DD).' example: '2026-01-01' - in: query name: joined_to description: 'Date fin adhésion (YYYY-MM-DD).' example: '2026-12-31' required: false schema: type: string description: 'Date fin adhésion (YYYY-MM-DD).' example: '2026-12-31' - in: query name: sort description: 'Champ de tri : member_number, last_name, joined_at.' example: member_number required: false schema: type: string description: 'Champ de tri : member_number, last_name, joined_at.' example: member_number - in: query name: order description: 'Ordre : asc ou desc.' example: asc required: false schema: type: string description: 'Ordre : asc ou desc.' example: asc - in: query name: per_page description: 'Résultats par page (max 100).' example: 25 required: false schema: type: integer description: 'Résultats par page (max 100).' example: 25 - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members post: summary: 'Créer un membre' operationId: crerUnMembre description: "Crée un nouveau membre. Le numéro de membre est généré automatiquement.\nEnvoyer les fichiers en multipart/form-data." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members requestBody: required: true content: multipart/form-data: schema: type: object properties: first_name: type: string description: Prénom. example: Kofi last_name: type: string description: 'Nom de famille.' example: Mensah email: type: string description: 'Email (unique par tenant).' example: kofi@example.com nullable: true phone: type: string description: Téléphone. example: '+22997000000' nullable: true gender: type: string description: 'Genre : M, F, other.' example: M nullable: true birth_date: type: string description: 'Date de naissance (YYYY-MM-DD).' example: '1990-01-15' nullable: true address: type: string description: validation.max. example: wgshlz nullable: true city: type: string description: validation.max. example: aedjxa nullable: true country: type: string description: 'Code pays ISO alpha-2.' example: BJ nullable: true profession: type: string description: validation.max. example: izxgnx nullable: true status: type: string description: Statut. example: pending nullable: true group_id: type: string description: 'UUID du groupe.' example: null nullable: true sponsor_id: type: string description: 'validation.uuid The id of an existing record in the members table.' example: 4b9c5595-1c58-3e6a-9a54-8e9361992634 nullable: true joined_at: type: string description: validation.date. example: '2026-05-04T13:50:09' nullable: true notes: type: string description: '' example: architecto nullable: true photo: type: string format: binary description: 'Photo membre (jpg/png, max 2MB).' nullable: true id_card: type: string format: binary description: "Pièce d'identité (jpg/png/pdf, max 5MB)." nullable: true required: - first_name - last_name '/api/v1/members/{id}': get: summary: "Détail d'un membre" operationId: dtailDunMembre description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members put: summary: 'Modifier un membre' operationId: modifierUnMembre description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members requestBody: required: false content: multipart/form-data: schema: type: object properties: first_name: type: string description: validation.max. example: vwgshl last_name: type: string description: validation.max. example: zaedjx email: type: string description: 'validation.email validation.max.' example: rosenbaum.javonte@example.com nullable: true phone: type: string description: validation.max. example: xgnxor nullable: true gender: type: string description: '' example: F enum: - M - F - other nullable: true birth_date: type: string description: 'validation.date validation.before.' example: '2026-03-12' nullable: true address: type: string description: validation.max. example: wgshlz nullable: true city: type: string description: validation.max. example: aedjxa nullable: true country: type: string description: validation.size. example: uu nullable: true profession: type: string description: validation.max. example: izxgnx nullable: true group_id: type: string description: 'validation.uuid The id of an existing record in the member_groups table.' example: 5b0c0231-0539-3fcd-86ff-38b36e835b89 nullable: true sponsor_id: type: string description: 'validation.uuid The id of an existing record in the members table. Must not be one of .' example: 4b9c5595-1c58-3e6a-9a54-8e9361992634 nullable: true joined_at: type: string description: validation.date. example: '2026-05-04T13:50:09' nullable: true notes: type: string description: '' example: architecto nullable: true photo: type: string format: binary description: 'Must be a file. validation.max.' nullable: true id_card: type: string format: binary description: 'Must be a file. validation.max.' nullable: true delete: summary: 'Supprimer un membre (soft delete)' operationId: supprimerUnMembresoftDelete description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members parameters: - in: path name: id description: 'UUID du membre.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/v1/members/{id}/restore': post: summary: 'Restaurer un membre supprimé' operationId: restaurerUnMembreSupprim description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members parameters: - in: path name: id description: 'UUID du membre.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/v1/members/{id}/status': put: summary: "Changer le statut d'un membre" operationId: changerLeStatutDunMembre description: '' parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members requestBody: required: true content: application/json: schema: type: object properties: status: type: string description: 'Nouveau statut : active, inactive, suspended, honorary, deceased.' example: active reason: type: string description: 'Raison du changement (optionnel).' example: 'Cotisations à jour' nullable: true required: - status parameters: - in: path name: id description: 'UUID du membre.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/v1/members/{id}/card': get: summary: 'Télécharger la carte membre PDF' operationId: tlchargerLaCarteMembrePDF description: "Génère et retourne la carte membre au format PDF (A6 paysage).\nLe QR code intégré pointe vers l'endpoint de vérification publique." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 200: description: 'Carte PDF' content: text/plain: schema: type: string example: 'file {"description": "Fichier PDF de la carte membre"}' tags: - Members parameters: - in: path name: id description: 'UUID du membre.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string '/api/v1/members/{id}/card/regenerate': post: summary: 'Régénérer le token QR code de la carte' operationId: rgnrerLeTokenQRCodeDeLaCarte description: "Invalide l'ancien QR code et génère un nouveau token." parameters: - in: header name: X-Tenant-Slug description: '' example: demo schema: type: string responses: 500: description: '' content: application/json: schema: type: object example: success: false message: 'Erreur serveur.' data: null errors: [] meta: timestamp: '2026-05-04T13:50:09+00:00' version: v1 properties: success: type: boolean example: false message: type: string example: 'Erreur serveur.' data: type: string example: null nullable: true errors: type: array example: [] meta: type: object properties: timestamp: type: string example: '2026-05-04T13:50:09+00:00' version: type: string example: v1 tags: - Members parameters: - in: path name: id description: 'UUID du membre.' example: 550e8400-e29b-41d4-a716-446655440000 required: true schema: type: string