{"openapi":"3.1.0","info":{"title":"API MaProg — façade agents IA","version":"1.0.0","description":"Façade REST unique pour piloter MaProg par programme (agent IA). Authentification par clé API `mpk_…` (header `Authorization: Bearer mpk_…`), obtenue via POST /v1/auth/login avec `create_api_key: true`. Horaires en millisecondes UTC (`debut`/`fin`) ; erreurs normalisées `{ \"error\": { \"code\", \"message\", \"details\" } }`. Spec canonique : /v1/openapi.json."},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","description":"Clé API MaProg (`mpk_…`) créée via POST /v1/auth/login (create_api_key) puis listée/révocable sur /v1/me/api-keys."}},"schemas":{"Erreur":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]}}}}},"paths":{"/v1/":{"get":{"description":"Racine de la façade : liens d'auto-découverte + état (`actif` ou `desactive` — coupure d'exploitation API_DISABLED, consultée par la page https://maprog.app/api).","responses":{"200":{"description":"Index","content":{"application/json":{"schema":{"description":"Index","type":"object","additionalProperties":true}}}}}}},"/v1/openapi.json":{"get":{"description":"Spécification OpenAPI 3.1 des routes /v1 (contrat canonique pour agents).","responses":{"200":{"description":"Document OpenAPI 3.1","content":{"application/json":{"schema":{"description":"Document OpenAPI 3.1","type":"object","additionalProperties":true}}}}}}},"/v1/docs":{"get":{"description":"UI Swagger lisant /v1/openapi.json.","responses":{"200":{"description":"Default Response"}}}},"/v1/auth/login":{"post":{"tags":["auth"],"description":"Connexion par mot de passe. `realm` = users (organisateur), spectateurs (compte spectateur) ou espace (équipe manifestation : `manif_id` + mot de passe). Avec `create_api_key: true`, crée et renvoie une clé API (scopes par défaut : tous ceux du royaume).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","password"],"properties":{"realm":{"type":"string","enum":["users","spectateurs","espace"]},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":1},"manif_id":{"type":"string","description":"Identifiant de manifestation (realm espace)"},"create_api_key":{"type":"boolean","default":false},"scopes":{"type":"array","items":{"type":"string"},"description":"Sous-ensemble des scopes du royaume (défaut : tous)"},"nom":{"type":"string","description":"Libellé repère de la clé"},"expires_in_days":{"type":"integer","minimum":1,"maximum":3650,"default":365}}}}}},"security":[],"responses":{"200":{"description":"Session + clé API éventuelle","content":{"application/json":{"schema":{"description":"Session + clé API éventuelle","type":"object","additionalProperties":true}}}}}}},"/v1/auth/signup":{"post":{"tags":["auth"],"description":"Création de compte (realm users = organisateur avec prénom/nom, ou spectateurs). Un email de vérification est envoyé — la connexion (et donc la clé API) exige la vérification.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","email","password"],"properties":{"realm":{"type":"string","enum":["users","spectateurs"]},"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8,"maxLength":72},"prenom":{"type":"string"},"nom":{"type":"string"},"email_alertes":{"type":"boolean"},"email_marketing":{"type":"boolean"}}}}}},"security":[],"responses":{"200":{"description":"Compte créé","content":{"application/json":{"schema":{"description":"Compte créé","type":"object","additionalProperties":true}}}}}}},"/v1/auth/verify-email":{"post":{"tags":["auth"],"description":"Confirme un email avec le token reçu (lien de vérification).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","token"],"properties":{"realm":{"type":"string","enum":["users","spectateurs"]},"token":{"type":"string"}}}}}},"security":[],"responses":{"200":{"description":"Email vérifié","content":{"application/json":{"schema":{"description":"Email vérifié","type":"object","additionalProperties":true}}}}}}},"/v1/auth/request-verification":{"post":{"tags":["auth"],"description":"Renvoie l'email de vérification.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","email"],"properties":{"realm":{"type":"string","enum":["users","spectateurs"]},"email":{"type":"string","format":"email"}}}}}},"security":[],"responses":{"200":{"description":"Email envoyé si le compte existe","content":{"application/json":{"schema":{"description":"Email envoyé si le compte existe","type":"object","additionalProperties":true}}}}}}},"/v1/auth/request-password-reset":{"post":{"tags":["auth"],"description":"Demande la réinitialisation du mot de passe (email envoyé si le compte existe).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","email"],"properties":{"realm":{"type":"string","enum":["users","spectateurs"]},"email":{"type":"string","format":"email"}}}}}},"security":[],"responses":{"200":{"description":"Email envoyé si le compte existe","content":{"application/json":{"schema":{"description":"Email envoyé si le compte existe","type":"object","additionalProperties":true}}}}}}},"/v1/auth/reset-password":{"post":{"tags":["auth"],"description":"Définit un nouveau mot de passe avec le token reçu par email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["realm","token","password"],"properties":{"realm":{"type":"string","enum":["users","spectateurs"]},"token":{"type":"string"},"password":{"type":"string","minLength":8,"maxLength":72}}}}}},"security":[],"responses":{"200":{"description":"Mot de passe modifié","content":{"application/json":{"schema":{"description":"Mot de passe modifié","type":"object","additionalProperties":true}}}}}}},"/v1/me":{"get":{"tags":["general"],"description":"Compte lié à la clé : royaume, scopes, profil public du compte.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Identité de la clé et du compte","content":{"application/json":{"schema":{"description":"Identité de la clé et du compte","type":"object","additionalProperties":true}}}}}}},"/v1/me/api-keys":{"get":{"tags":["auth"],"description":"Liste les clés du compte (jamais la clé elle-même).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Clés du compte","content":{"application/json":{"schema":{"description":"Clés du compte","type":"object","additionalProperties":true}}}}}},"post":{"tags":["auth"],"description":"Crée une nouvelle clé pour le compte courant (scopes par défaut : tous ceux du royaume). La clé `mpk_…` n'est renvoyée qu'ici, une seule fois.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nom":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"expires_in_days":{"type":"integer","minimum":1,"maximum":3650}}}}}},"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Clé créée (cle en clair, unique restitution)","content":{"application/json":{"schema":{"description":"Clé créée (cle en clair, unique restitution)","type":"object","additionalProperties":true}}}}}}},"/v1/me/api-keys/{id}":{"delete":{"tags":["auth"],"description":"Révoque une clé du compte courant.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Clé révoquée","content":{"application/json":{"schema":{"description":"Clé révoquée","type":"object","additionalProperties":true}}}}}}},"/v1/me/manifestations":{"get":{"tags":["orga"],"description":"Manifestations des structures administrées par le compte (règles PB : structure.admin).","parameters":[{"schema":{"type":"integer"},"in":"query","name":"page","required":false},{"schema":{"type":"integer"},"in":"query","name":"per_page","required":false}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Manifestations du compte","content":{"application/json":{"schema":{"description":"Manifestations du compte","type":"object","additionalProperties":true}}}}}}},"/v1/me/plannings":{"get":{"tags":["spectateur"],"description":"Plannings cloud du spectateur : { [manifUrl]: [evtId, …] }.","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Plannings par manifestation","content":{"application/json":{"schema":{"description":"Plannings par manifestation","type":"object","additionalProperties":true}}}}}},"put":{"tags":["spectateur"],"description":"Écrit les plannings cloud. `merge: \"union\"` fusionne avec l'existant par manif (sémantique du merge 3 voies de l'UI), `replace` (défaut) remplace intégralement.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["plannings"],"properties":{"plannings":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},"merge":{"type":"string","enum":["replace","union"]}}}}}},"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"État final des plannings","content":{"application/json":{"schema":{"description":"État final des plannings","type":"object","additionalProperties":true}}}}}}},"/v1/me/favoris":{"get":{"tags":["spectateur"],"description":"Manifestations favorites du spectateur (ids PB).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Favoris","content":{"application/json":{"schema":{"description":"Favoris","type":"object","additionalProperties":true}}}}}},"put":{"tags":["spectateur"],"description":"Écrit les favoris (ids PB de manifestations). merge union/replace comme /me/plannings.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["favoris"],"properties":{"favoris":{"type":"array","items":{"type":"string"}},"merge":{"type":"string","enum":["replace","union"]}}}}}},"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"État final des favoris","content":{"application/json":{"schema":{"description":"État final des favoris","type":"object","additionalProperties":true}}}}}}},"/v1/me/plannings/{manifUrl}/conflits":{"get":{"tags":["spectateur"],"description":"Conflits horaires du planning de la manifestation donnée (chevauchements d'événements, miroir du calcul de l'UI sur les instants absolus).","parameters":[{"schema":{"type":"string"},"in":"path","name":"manifUrl","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Paires d'événements en conflit","content":{"application/json":{"schema":{"description":"Paires d'événements en conflit","type":"object","additionalProperties":true}}}}}}},"/v1/me/groupes":{"get":{"tags":["spectateur"],"description":"Groupes dont le spectateur est membre (miroir de « Mes groupes » du compte).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Groupes du spectateur","content":{"application/json":{"schema":{"description":"Groupes du spectateur","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations":{"get":{"tags":["public"],"description":"Catalogue public des manifestations (abonnement actif + is_public). `q` filtre sur le nom ; `lat`/`lng`/`radius` (km) trient par distance géolocalisée (miroir de l'« autour de moi » mobile).","parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false},{"schema":{"type":"number"},"in":"query","name":"lat","required":false},{"schema":{"type":"number"},"in":"query","name":"lng","required":false},{"schema":{"type":"number"},"in":"query","name":"radius","required":false,"description":"Rayon en kilomètres (défaut 20)"},{"schema":{"type":"integer"},"in":"query","name":"page","required":false},{"schema":{"type":"integer"},"in":"query","name":"per_page","required":false}],"security":[],"responses":{"200":{"description":"Manifestations publiques","content":{"application/json":{"schema":{"description":"Manifestations publiques","type":"object","additionalProperties":true}}}}}},"post":{"tags":["orga"],"description":"Crée une manifestation (même contrat que l'assistant : logo + email de contact requis par le schéma PocketBase ; pages_actives/page_index par défaut « prog »). Fournir `google_sheet` (URL d'un classeur partagé au compte de service) OU `sheet_template: true` pour copier le Sheet modèle (recommandé).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom","url","structure","contact_email","logo_b64"],"properties":{"nom":{"type":"string","minLength":1},"url":{"type":"string","description":"Slug public (maprog.app/<url>)"},"structure":{"type":"string","description":"Id de la structure propriétaire"},"google_sheet":{"type":"string"},"sheet_template":{"type":"boolean","description":"Copier le Sheet modèle et l'utiliser"},"page_index":{"type":"string","enum":["prog","faq","a-propos","blog","contact"]},"pages_actives":{"type":"array","items":{"type":"string"}},"contact_email":{"type":"string","format":"email"},"themes":{"type":"array","items":{"type":"string"}},"media_ratio":{"type":"string","enum":["2/3","16/9"]},"logo_b64":{"type":"string","description":"Logo en base64 (data URL acceptée)"}}}}}},"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Manifestation créée","content":{"application/json":{"schema":{"description":"Manifestation créée","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/url/{url}":{"get":{"tags":["public"],"description":"Manifestation par son slug public (celui de l'URL du site).","parameters":[{"schema":{"type":"string"},"in":"path","name":"url","required":true}],"security":[],"responses":{"200":{"description":"Manifestation","content":{"application/json":{"schema":{"description":"Manifestation","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}":{"get":{"tags":["public"],"description":"Manifestation par identifiant PocketBase.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Manifestation","content":{"application/json":{"schema":{"description":"Manifestation","type":"object","additionalProperties":true}}}}}},"patch":{"tags":["orga"],"description":"Modifie une manifestation. Champs texte/JSON passés tels quels + `logo_b64`/`banner_b64` (data base64, la façade construit le multipart PocketBase). is_public passe par POST /manifestations/:id/publication.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Champs : nom, url, structure, google_sheet, lien_externe, pages_actives, page_index, media_ratio, themes, prog_header, prog_lower, a_propos, contact_*, blog_est_externe, blog_externe, logo_b64, banner_b64"}}},"description":"Champs : nom, url, structure, google_sheet, lien_externe, pages_actives, page_index, media_ratio, themes, prog_header, prog_lower, a_propos, contact_*, blog_est_externe, blog_externe, logo_b64, banner_b64"},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Manifestation modifiée","content":{"application/json":{"schema":{"description":"Manifestation modifiée","type":"object","additionalProperties":true}}}}}},"delete":{"tags":["orga"],"description":"Supprime une manifestation (admin de la structure).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/manifestations/{id}/evenements":{"get":{"tags":["public"],"description":"Programme normalisé (view-model Evenement de l'UI). Filtres multi-valeurs identiques à la page prog (répéter le paramètre ou séparer par virgules) ; facettes = valeurs disponibles par dimension. Tri par debut croissant.","parameters":[{"schema":{"type":"string"},"in":"query","name":"q","required":false,"description":"Recherche floue (moteur Fuse de l'UI)"},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"date","required":false,"description":"YYYY-MM-DD ou DD/MM/YYYY"},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"lieu","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"section","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"categorie","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"mot","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"icone","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"accessibilite","required":false},{"schema":{"type":"array","items":{"type":"string"}},"in":"query","name":"restriction","required":false},{"schema":{"type":"boolean"},"in":"query","name":"a_venir","required":false,"description":"Ne garder que les événements à venir (debut >= maintenant)"},{"schema":{"type":"integer"},"in":"query","name":"page","required":false},{"schema":{"type":"integer"},"in":"query","name":"per_page","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Événements paginés + facettes","content":{"application/json":{"schema":{"description":"Événements paginés + facettes","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/evenements/{evtId}":{"get":{"tags":["public"],"description":"Un événement du programme (view-model Evenement).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"evtId","required":true,"description":"identifiant evt_…"}],"security":[],"responses":{"200":{"description":"Événement","content":{"application/json":{"schema":{"description":"Événement","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/faq":{"get":{"tags":["public"],"description":"FAQ de la manifestation (feuille FAQ du Google Sheet).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Questions/réponses","content":{"application/json":{"schema":{"description":"Questions/réponses","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/blog":{"get":{"tags":["public"],"description":"Blog de la manifestation. Mode interne : articles normalisés (titre/extrait/contenu/date). Mode externe : URL du flux + lien vers la vue normalisée GET /rss/externe/{url}.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Blog (interne ou externe)","content":{"application/json":{"schema":{"description":"Blog (interne ou externe)","type":"object","additionalProperties":true}}}}}},"put":{"tags":["orga"],"description":"Configure le blog. mode interne : remplace les articles (ids générés si absents, HTML des contenu tel quel — assaini à l'affichage). mode externe : URL du flux RSS/Atom.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mode"],"properties":{"mode":{"type":"string","enum":["interne","externe"]},"articles":{"type":"array","items":{"type":"object","required":["titre","contenu"],"properties":{"id":{"type":"string"},"titre":{"type":"string"},"contenu":{"type":"string","description":"HTML"},"date":{"type":"integer","description":"ms epoch"}}}},"externe":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Blog enregistré","content":{"application/json":{"schema":{"description":"Blog enregistré","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/lieux":{"get":{"tags":["public"],"description":"Lieux distincts du programme avec coordonnées (dérivés des événements — même source que la vue Carte).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Lieux","content":{"application/json":{"schema":{"description":"Lieux","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/planning.ics":{"get":{"tags":["public"],"description":"Export iCalendar d'une sélection d'événements (miroir des liens de planning partagé `?planning=evt1,evt2`). Content-Type: text/calendar.","parameters":[{"schema":{"type":"string"},"in":"query","name":"events","required":false,"description":"Ids evt_… séparés par virgules (vide = tout le programme)"},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Default Response"}}}},"/v1/structures":{"get":{"tags":["orga"],"description":"Mes structures (règle PB : admin du compte).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Structures administrées","content":{"application/json":{"schema":{"description":"Structures administrées","type":"object","additionalProperties":true}}}}}},"post":{"tags":["orga"],"description":"Crée une structure (SIRET validé par Luhn, comme le formulaire ; siege social requis par le schéma PocketBase). Le compte courant devient admin.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["denomination_sociale","siret","siege_social_adresse","siege_social_code_postal","siege_social_ville"],"properties":{"denomination_sociale":{"type":"string","minLength":1,"maxLength":200},"siret":{"type":"string","pattern":"^\\d{14}$"},"siege_social_adresse":{"type":"string"},"siege_social_code_postal":{"type":"string"},"siege_social_ville":{"type":"string"},"siege_social_complement":{"type":"string"},"site_web":{"type":"string","format":"uri"},"contact_email":{"type":"string","format":"email"}}}}}},"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Structure créée","content":{"application/json":{"schema":{"description":"Structure créée","type":"object","additionalProperties":true}}}}}}},"/v1/structures/{id}":{"get":{"tags":["orga"],"description":"Une structure par id (admin seulement).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Structure","content":{"application/json":{"schema":{"description":"Structure","type":"object","additionalProperties":true}}}}}},"patch":{"tags":["orga"],"description":"Modifie une structure (champs passés).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"description":"Champs modifiables : denomination_sociale, siret, siege_social_*, site_web, contact_email"}}},"description":"Champs modifiables : denomination_sociale, siret, siege_social_*, site_web, contact_email"},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Structure modifiée","content":{"application/json":{"schema":{"description":"Structure modifiée","type":"object","additionalProperties":true}}}}}},"delete":{"tags":["orga"],"description":"Supprime une structure (cascade PocketBase sur ses manifestations).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Default Response"}}}},"/v1/manifestations/{id}/publication":{"post":{"tags":["orga"],"description":"Publie (`public: true`) ou dépublie la manifestation. La publication exige un abonnement actif/trialing (ou dérogation) — sinon 402 SUBSCRIPTION_REQUIRED.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["public"],"properties":{"public":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"État de publication","content":{"application/json":{"schema":{"description":"État de publication","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/refresh":{"post":{"tags":["orga"],"description":"Force la resynchronisation du Google Sheet vers le cache `service_sheets` (contourne le garde modifiedTime, comme le bouton admin). Le programme reste éditable uniquement dans le Sheet (parité UI).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Résultat du refresh","content":{"application/json":{"schema":{"description":"Résultat du refresh","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/validation":{"get":{"tags":["orga"],"description":"Rapport de validation des données du Google Sheet : erreurs/alertes avec coordonnées A1 (miroir de l'onglet Google Sheet → Valider).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Rapport { errors, warnings, summary }","content":{"application/json":{"schema":{"description":"Rapport { errors, warnings, summary }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/checklist":{"get":{"tags":["orga"],"description":"Checklist d'onboarding de la manifestation (JSON tel que stocké).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Checklist","content":{"application/json":{"schema":{"description":"Checklist","type":"object","additionalProperties":true}}}}}},"put":{"tags":["orga"],"description":"Remplace la checklist (objet JSON — même structure que celle lue en GET).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["checklist"],"properties":{"checklist":{"type":"object","additionalProperties":true}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Checklist enregistrée","content":{"application/json":{"schema":{"description":"Checklist enregistrée","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/statistiques":{"get":{"tags":["orga"],"description":"Statistiques d'audience Plausible de la manifestation (miroir de l'onglet Statistiques) : date_range=30d|90d|…, start/end=YYYY-MM-DD, events=1 pour le détail par événement.","parameters":[{"schema":{"type":"string"},"in":"query","name":"date_range","required":false},{"schema":{"type":"string"},"in":"query","name":"start","required":false},{"schema":{"type":"string"},"in":"query","name":"end","required":false},{"schema":{"type":"string","enum":["1"]},"in":"query","name":"events","required":false},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Statistiques Plausible","content":{"application/json":{"schema":{"description":"Statistiques Plausible","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/espace":{"get":{"tags":["orga"],"description":"Existence du compte équipe (espace fréquentation) de la manifestation.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Existe / n'existe pas","content":{"application/json":{"schema":{"description":"Existe / n'existe pas","type":"object","additionalProperties":true}}}}}},"post":{"tags":["orga"],"description":"Crée (ou réinitialise avec regenerate) le compte équipe de la manifestation. Le lien de connexion est /espace/{manifId} sur le site ; l'API le consomme via realm=espace.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":4},"regenerate":{"type":"boolean"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Compte équipe créé/réinitialisé","content":{"application/json":{"schema":{"description":"Compte équipe créé/réinitialisé","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/contact":{"post":{"tags":["orga"],"description":"Formulaire de contact d'une manifestation (envoie un email à contact_email).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","message"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"message":{"type":"string","minLength":1}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Message envoyé","content":{"application/json":{"schema":{"description":"Message envoyé","type":"object","additionalProperties":true}}}}}}},"/v1/contact":{"post":{"tags":["orga"],"description":"Formulaire de contact général MaProg.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","subject","message"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"company":{"type":"string"},"subject":{"type":"string"},"message":{"type":"string","minLength":1}}}}}},"security":[],"responses":{"200":{"description":"Message envoyé","content":{"application/json":{"schema":{"description":"Message envoyé","type":"object","additionalProperties":true}}}}}}},"/v1/stripe/products":{"get":{"tags":["stripe"],"description":"Produits et prix d'abonnement actifs (miroir de l'onglet Abonnement).","security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ products: [{ id, name, description, prices }] }","content":{"application/json":{"schema":{"description":"{ products: [{ id, name, description, prices }] }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/abonnement":{"get":{"tags":["stripe"],"description":"État de l'abonnement de la manifestation (statut Stripe + isSubscriptionValid).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Abonnement","content":{"application/json":{"schema":{"description":"Abonnement","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/checkout":{"post":{"tags":["stripe"],"description":"Crée une session Stripe Checkout (abonnement). Renvoie { url } — la finalisation du paiement exige un humain avec un navigateur ; l'état se vérifie ensuite sur /manifestations/:id/abonnement.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["price_id"],"properties":{"price_id":{"type":"string","description":"Id de prix (GET /v1/stripe/products)"},"origin":{"type":"string","description":"Origine des URL de retour (défaut : site web)"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"description":"{ url }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/portal":{"post":{"tags":["stripe"],"description":"Session du portail de facturation Stripe de la structure propriétaire. Renvoie { url } (navigation humaine).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"origin":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"description":"{ url }","type":"object","additionalProperties":true}}}}}}},"/v1/structures/{id}/portal":{"post":{"tags":["stripe"],"description":"Portail de facturation au niveau structure. Renvoie { url } (navigation humaine).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"origin":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ url }","content":{"application/json":{"schema":{"description":"{ url }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos":{"get":{"tags":["medias"],"description":"Vidéos de la manifestation (statuts + URLs de lecture) et quota de stockage (5 Go).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ assets, quota }","content":{"application/json":{"schema":{"description":"{ assets, quota }","type":"object","additionalProperties":true}}}}}},"post":{"tags":["medias"],"description":"Crée une vidéo + URL d'upload presignée (PUT direct vers S3 avec le content_type renvoyé), puis POST …/upload-complete. Les gros fichiers passent obligatoirement par ce chemin.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["size_bytes"],"properties":{"titre":{"type":"string"},"size_bytes":{"type":"integer","minimum":1}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ asset, upload_url, content_type }","content":{"application/json":{"schema":{"description":"{ asset, upload_url, content_type }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos/{assetId}/upload-complete":{"post":{"tags":["medias"],"description":"Confirme l'upload presigné (vérification HEAD S3 + re-check réel du quota).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ asset }","content":{"application/json":{"schema":{"description":"{ asset }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos/{assetId}/process":{"post":{"tags":["medias"],"description":"Lance le transcodage HLS (idempotent : created → 409, en cours/terminé → asset tel quel). Statuts : created → uploaded → queued/processing → ready | error. Sondage recommandé : GET …/medias/videos toutes les 5 s.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ asset }","content":{"application/json":{"schema":{"description":"{ asset }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos/{assetId}/attach":{"post":{"tags":["medias"],"description":"Attribue la vidéo (prête) à un événement : ajoute le manifest HLS à la cellule Visuel du Sheet et rafraîchit le programme (temps réel).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event_id"],"properties":{"event_id":{"type":"string","description":"evt_…"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, row }","content":{"application/json":{"schema":{"description":"{ ok, row }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos/{assetId}/detach":{"post":{"tags":["medias"],"description":"Retire la vidéo d'un événement (event_id) ou de tous les événements (sans event_id).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"event_id":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, rows }","content":{"application/json":{"schema":{"description":"{ ok, rows }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/videos/{assetId}":{"delete":{"tags":["medias"],"description":"Supprime la vidéo (S3 + Hovod) après purge best-effort du Visuel.","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"assetId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, detached_rows }","content":{"application/json":{"schema":{"description":"{ ok, detached_rows }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/images":{"get":{"tags":["medias"],"description":"Images stockées de la manifestation (clés S3, tailles).","parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ images }","content":{"application/json":{"schema":{"description":"{ images }","type":"object","additionalProperties":true}}}}}},"post":{"tags":["medias"],"description":"Crée une clé d'image + URL d'upload presignée (PUT direct S3, content_type signé). Alternative agent : PUT …/medias/images/upload en base64.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","content_type","size_bytes"],"properties":{"filename":{"type":"string"},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/gif"]},"size_bytes":{"type":"integer","minimum":1}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ key, upload_url, content_type, url }","content":{"application/json":{"schema":{"description":"{ key, upload_url, content_type, url }","type":"object","additionalProperties":true}}}}}},"delete":{"tags":["medias"],"description":"Supprime l'objet S3 (clé en query, elle contient des slashes) après purge du Visuel.","parameters":[{"schema":{"type":"string"},"in":"query","name":"key","required":true},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, detached_rows }","content":{"application/json":{"schema":{"description":"{ ok, detached_rows }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/images/upload":{"post":{"tags":["medias"],"description":"Upload direct d'une image par la façade (base64, ≤ 20 Mo — JPEG/PNG/WebP/GIF). content_type déduit du préfixe data: ou de l'extension du filename. Renvoie la clé + l'URL imgproxy prête à attacher.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","content_b64"],"properties":{"filename":{"type":"string"},"content_b64":{"type":"string","description":"base64 brut ou data URL"},"content_type":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ key, url }","content":{"application/json":{"schema":{"description":"{ key, url }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/images/attach":{"post":{"tags":["medias"],"description":"Attribue l'image à un événement (URL imgproxy ajoutée au Visuel + refresh).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","event_id"],"properties":{"key":{"type":"string"},"event_id":{"type":"string","description":"evt_…"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, row }","content":{"application/json":{"schema":{"description":"{ ok, row }","type":"object","additionalProperties":true}}}}}}},"/v1/manifestations/{id}/medias/images/detach":{"post":{"tags":["medias"],"description":"Retire l'image d'un événement (event_id) ou de tous (sans event_id).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key"],"properties":{"key":{"type":"string"},"event_id":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, rows }","content":{"application/json":{"schema":{"description":"{ ok, rows }","type":"object","additionalProperties":true}}}}}}},"/v1/espace/{manifId}/feuille":{"get":{"tags":["espace"],"description":"Liste des événements avec valeurs terrain (fréquentation, jauge, nombreSpectateurs, note) — lecture directe du Google Sheet, `row` est 1-based.","parameters":[{"schema":{"type":"string"},"in":"path","name":"manifId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ manifestation, events }","content":{"application/json":{"schema":{"description":"{ manifestation, events }","type":"object","additionalProperties":true}}}}}},"post":{"tags":["espace"],"description":"Écrit des cellules terrain par lot. field ∈ frequentation | jauge | nombreSpectateurs | note | visuel ; row ≥ 2 (1-based). Les updates invalides sont ignorées (miroir de /manif-admin/sheet).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["updates"],"properties":{"updates":{"type":"array","items":{"type":"object","required":["row","field","value"],"properties":{"row":{"type":"integer","minimum":2},"field":{"type":"string","enum":["frequentation","jauge","nombreSpectateurs","note","visuel"]},"value":{}}}}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"manifId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"{ ok, written }","content":{"application/json":{"schema":{"description":"{ ok, written }","type":"object","additionalProperties":true}}}}}}},"/v1/espace/{manifId}/blog":{"get":{"tags":["espace"],"description":"Articles du blog interne (rédaction équipe).","parameters":[{"schema":{"type":"string"},"in":"path","name":"manifId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Articles","content":{"application/json":{"schema":{"description":"Articles","type":"object","additionalProperties":true}}}}}},"put":{"tags":["espace"],"description":"Remplace TOUS les articles du blog interne (remplacement atomique, 256 Ko / 500 articles max — garde PB).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["articles"],"properties":{"articles":{"type":"array","items":{"type":"object","required":["titre","contenu"],"properties":{"id":{"type":"string"},"titre":{"type":"string"},"contenu":{"type":"string","description":"HTML"},"date":{"type":"integer","description":"ms epoch"}}}}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"manifId","required":true}],"security":[{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Articles enregistrés","content":{"application/json":{"schema":{"description":"Articles enregistrés","type":"object","additionalProperties":true}}}}}}},"/v1/groupes":{"post":{"tags":["spectateur"],"description":"Crée un groupe pour une manifestation. `nom` = votre pseudo dans le groupe. `cle` facultative (mbr_… ou id spectateur) — sinon générée et renvoyée. Conservez-la : c'est votre capacité d'écriture (pousser son planning, quitter).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["manifestation","nom"],"properties":{"manifestation":{"type":"string","description":"Id PB de la manifestation"},"titre":{"type":"string","maxLength":80},"nom":{"type":"string","minLength":1,"maxLength":40},"cle":{"type":"string"}}}}}},"security":[],"responses":{"200":{"description":"Groupe créé + votre_cle","content":{"application/json":{"schema":{"description":"Groupe créé + votre_cle","type":"object","additionalProperties":true}}}}}}},"/v1/groupes/{id}/rejoindre":{"post":{"tags":["spectateur"],"description":"Rejoint un groupe (idempotent). `ids` facultatif = planning initial ; `ancienne_cle` migre un membership anonyme vers votre compte (union des ids). `cle` facultative — générée et renvoyée si absente.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["nom"],"properties":{"nom":{"type":"string","minLength":1,"maxLength":40},"ids":{"type":"array","items":{"type":"string"}},"cle":{"type":"string"},"ancienne_cle":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Groupe + votre_cle","content":{"application/json":{"schema":{"description":"Groupe + votre_cle","type":"object","additionalProperties":true}}}}}}},"/v1/groupes/{id}/planning":{"post":{"tags":["spectateur"],"description":"Remplace le planning du membre appelant (500 ids max). Ne touche QUE votre entrée — les autres membres ne sont pas impactés (propagés par temps réel côté UI).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cle","ids"],"properties":{"cle":{"type":"string"},"ids":{"type":"array","items":{"type":"string"},"maxItems":500}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"{ ok }","content":{"application/json":{"schema":{"description":"{ ok }","type":"object","additionalProperties":true}}}}}}},"/v1/groupes/{id}/quitter":{"post":{"tags":["spectateur"],"description":"Quitte le groupe (idempotent) : retire votre planning et votre membership.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cle"],"properties":{"cle":{"type":"string"}}}}}},"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"{ ok }","content":{"application/json":{"schema":{"description":"{ ok }","type":"object","additionalProperties":true}}}}}}},"/v1/groupes/{id}":{"get":{"tags":["spectateur"],"description":"Lit un groupe (l'id est la capability — comme le lien d'invitation). `cles` sélectionne des membres pour l'intersection (sinon : tous). Renvoie plannings, ids communs, présence par événement et le détail des événements concernés.","parameters":[{"schema":{"type":"string"},"in":"query","name":"cles","required":false,"description":"Clés membres séparées par virgules"},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"security":[],"responses":{"200":{"description":"Groupe + communs + événements","content":{"application/json":{"schema":{"description":"Groupe + communs + événements","type":"object","additionalProperties":true}}}}}}}},"servers":[{"url":"https://api.maprog.app","description":"production"},{"url":"http://localhost:3000","description":"développement local"}],"tags":[{"name":"general","description":"Racine, spec OpenAPI, compte lié à la clé"},{"name":"auth","description":"Connexion, inscription, vérification email, mot de passe, clés API"},{"name":"public","description":"Lectures publiques : manifestations, événements, FAQ, blog, lieux"},{"name":"orga","description":"Organisateur : structures, manifestations, programme, publication, statistiques"},{"name":"medias","description":"Vidéos (Hovod/HLS) et images (S3/imgproxy) des manifestations"},{"name":"stripe","description":"Produits, abonnement, checkout et portail facturation"},{"name":"espace","description":"Espace équipe manifestation (fréquentation, blog interne)"},{"name":"spectateur","description":"Planning/favoris cloud, groupes de partage"}]}