{"openapi":"3.1.0","info":{"title":"zinema","version":"0.1.0"},"paths":{"/api/auth/complete":{"post":{"summary":"Auth Complete","operationId":"auth_complete_api_auth_complete_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/google":{"post":{"summary":"Auth Google","operationId":"auth_google_api_auth_google_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/google/device/start":{"post":{"summary":"Auth Google Device Start","operationId":"auth_google_device_start_api_auth_google_device_start_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/google/device/poll":{"post":{"summary":"Auth Google Device Poll","description":"Device-flow étape 2 : le device poste son device_code ; le serveur l'échange contre un token Google\n(client_id + SECRET, gardés serveur). `pending` tant que l'user n'a pas autorisé ; à l'autorisation →\naccess_token → userinfo → compte Google → SESSION (même contrat que /api/auth/google).","operationId":"auth_google_device_poll_api_auth_google_device_poll_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/apple":{"post":{"summary":"Auth Apple","operationId":"auth_apple_api_auth_apple_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/plex/link":{"post":{"summary":"Auth Plex Link","operationId":"auth_plex_link_api_auth_plex_link_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/plex/merge":{"post":{"summary":"Auth Plex Merge","operationId":"auth_plex_merge_api_auth_plex_merge_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/request":{"post":{"summary":"Auth Email Request","description":"Login email : envoie un magic-link (token Fernet TTL 15 min) à l'adresse, via Resend.\nRéponse volontairement neutre (on ne révèle pas si l'email existe déjà).","operationId":"auth_email_request_api_auth_email_request_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/verify":{"get":{"summary":"Auth Email Verify","description":"Clic sur le magic-link : valide le token → find-or-create le compte (auth_identity email) →\npose le cookie de session et redirige vers l'app. Lien expiré/altéré → retour landing.\ndev = nonce d'appairage tvOS (flux device) : le clic — fait sur le TÉLÉPHONE — valide aussi\nla TV qui poll ; le téléphone repart connecté lui aussi (deux sessions pour un clic).","operationId":"auth_email_verify_api_auth_email_verify_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}},{"name":"dev","in":"query","required":false,"schema":{"type":"string","default":"","title":"Dev"}},{"name":"wl","in":"query","required":false,"schema":{"type":"string","default":"","title":"Wl"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/device":{"post":{"summary":"Auth Email Device","description":"tvOS/iOS : envoie le magic-link PORTEUR d'un nonce d'appairage. Mêmes rate-limits que le\nweb (le coût Resend et la réputation du domaine ne changent pas de plateforme).","operationId":"auth_email_device_api_auth_email_device_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/email/poll":{"get":{"summary":"Auth Email Poll","description":"La TV poll : 202 tant que le lien n'est pas cliqué, 410 si nonce inconnu/expiré,\n200 {token} = session tvOS posée (nonce consommé, one-shot).","operationId":"auth_email_poll_api_auth_email_poll_get","parameters":[{"name":"nonce","in":"query","required":false,"schema":{"type":"string","default":"","title":"Nonce"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/auth/me":{"get":{"summary":"Auth Me","operationId":"auth_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Auth Me","operationId":"auth_me_api_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/logout":{"post":{"summary":"Auth Logout","operationId":"auth_logout_api_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/export":{"get":{"summary":"Export Data","description":"Export JSON de TOUT ce que zinema détient sur le compte (paquet confiance + portabilité).\nMiroir de la page « données » : profil, connexions (méta SEULEMENT — jamais les tokens, chiffrés\net non exportables), préférences, watchlist, événements de navigation. Bouton dans Réglages web.\n(22/10, profils, PROFILS_spec.md 4.6 et 8.3) Depuis le principal seulement : 403 depuis tout profil ajouté (l'export\nsort l'adresse du titulaire et les événements de tous les profils), avant le quota. Un compte qui a des profils\najoutés reçoit en plus `profils` (pseudo, couleur, tête depuis le 23/10, âge, « avec un adulte », mois de création)\net, sur chaque ligne de sa liste, `profil`, le pseudo de la liste qui la porte (celui du principal, ou null s'il\nn'en a pas) ; sans\nprofil ajouté, l'export d'avant, à l'octet hors genere_le. `feed_removed`, que la politique annonce aussi (8.3),\nattend le lot de la politique (tranché par le plan) : l'ajouter ici changerait dès aujourd'hui l'export de tout\ncompte qui a retiré un film.","operationId":"export_data_api_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Export Data","description":"Export JSON de TOUT ce que zinema détient sur le compte (paquet confiance + portabilité).\nMiroir de la page « données » : profil, connexions (méta SEULEMENT — jamais les tokens, chiffrés\net non exportables), préférences, watchlist, événements de navigation. Bouton dans Réglages web.\n(22/10, profils, PROFILS_spec.md 4.6 et 8.3) Depuis le principal seulement : 403 depuis tout profil ajouté (l'export\nsort l'adresse du titulaire et les événements de tous les profils), avant le quota. Un compte qui a des profils\najoutés reçoit en plus `profils` (pseudo, couleur, tête depuis le 23/10, âge, « avec un adulte », mois de création)\net, sur chaque ligne de sa liste, `profil`, le pseudo de la liste qui la porte (celui du principal, ou null s'il\nn'en a pas) ; sans\nprofil ajouté, l'export d'avant, à l'octet hors genere_le. `feed_removed`, que la politique annonce aussi (8.3),\nattend le lot de la politique (tranché par le plan) : l'ajouter ici changerait dès aujourd'hui l'export de tout\ncompte qui a retiré un film.","operationId":"export_data_api_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/auth/delete":{"post":{"summary":"Auth Delete","description":"Suppression de compte — OBLIGATOIRE App Store (guideline 5.1.1(v)) + propre côté RGPD. IRRÉVERSIBLE :\npurge le user ET toutes ses données. Toutes les tables liées (auth_identities, connections, plex_resources,\nevents, watchlist, sessions, reco_cache, account_provenance, match_*) sont en ON DELETE CASCADE depuis\nusers → un seul DELETE suffit. On ne touche JAMAIS au compte Plex de l'utilisateur (séparé), ni aux items /\nBA globaux (partagés).\n(11/10, Apple TV 1.14, lot S, D8) D'abord, le jeton Sign in with Apple gardé est révoqué chez Apple\n(_siwa_revoque) : sans cela la suppression n'était pas conforme. Elle n'attend que cette révocation et ne\ndépend jamais de son issue.\n(22/10, profils, PROFILS_spec.md 4.6 et 8.3) Depuis le principal seulement : supprimer le compte efface tout le\nfoyer, profils et listes compris (la cascade de users). 403 depuis tout profil ajouté, AVANT la révocation chez\nApple ; jamais un 401, qui purgerait la session des applis.","operationId":"auth_delete_api_auth_delete_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/feed":{"get":{"summary":"Get Feed","description":"(22/10, profils, 4.5) La route du fil : la page de _get_feed_page, plus le champ `profil` sur toute page\nrendue (un dict), jamais sur une réponse d'erreur. _get_feed_page est le corps d'avant : il passe par\nresolve_user, qui pose request.state, et _profil_champ le relit sans requête de plus. (22/10, profils, 4.2) Seul\nchangement de ce corps : le profil de la session, lu juste après resolve_user et passé à l'âge et au type.","operationId":"get_feed_api_feed_get","parameters":[{"name":"cursor","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Cursor"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","default":8,"title":"Page Size"}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}},{"name":"clips","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Clips"}},{"name":"embed","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Embed"}},{"name":"pin","in":"query","required":false,"schema":{"type":"string","default":"","title":"Pin"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/intro/{item_id}":{"get":{"summary":"Get Intro","description":"Offset (s) du 1er contenu d'un trailer (saut du temps mort), si déjà analysé.\n`known`=False tant que l'analyse de fond n'a pas tourné → le client réessaie au loop.","operationId":"get_intro_api_intro__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Intro","description":"Offset (s) du 1er contenu d'un trailer (saut du temps mort), si déjà analysé.\n`known`=False tant que l'analyse de fond n'a pas tourné → le client réessaie au loop.","operationId":"get_intro_api_intro__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deeplink/{item_id}":{"get":{"summary":"Get Deeplink","description":"Deep-link À LA DEMANDE (tap CTA « Regarder sur X » à froid depuis le feed, sans ouverture de\nfiche) : région résolue → deep-links du titre (lazy + cache film_deeplinks). Quota consommé QUE sur\nles films réellement cliqués. Renvoie {deeplinks: [...]|null}.","operationId":"get_deeplink_api_deeplink__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Deeplink","description":"Deep-link À LA DEMANDE (tap CTA « Regarder sur X » à froid depuis le feed, sans ouverture de\nfiche) : région résolue → deep-links du titre (lazy + cache film_deeplinks). Quota consommé QUE sur\nles films réellement cliqués. Renvoie {deeplinks: [...]|null}.","operationId":"get_deeplink_api_deeplink__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/film/seances":{"get":{"summary":"Film Seances","description":"« Où joue ce film » (19/08, décision Thierry sur la page des sorties) : les séances d'UN\nfilm à travers les grilles des 270 lieux. Deux situations, deux tris :\n  - `cines` (les slugs gardés du client, mode lane salle) → SES cinémas seulement ;\n  - `lat`/`lon` (géoloc AU GESTE, jamais au chargement) → les plus proches d'abord ;\n  - rien → les lieux à la prochaine séance, avec la ville en repère.\nPublic et sans auth comme les pages qu'il sert. Lecture seule, ~270 lignes, l'index GIN\nn'est même pas nécessaire. DÉCLARÉ AVANT /api/film/{item_id} : la paramétrique avalait\n« seances » comme un item_id et répondait 401 — même piège d'ordre que /c/{slug}, et la\npremière version l'a payé au premier test. Les coordonnées viennent de salles_ref par le slug partagé ;\nun lieu déclaré à la main (le Gong) n'y est pas → km nul, il reste listé.","operationId":"film_seances_api_film_seances_get","parameters":[{"name":"tmdb","in":"query","required":true,"schema":{"type":"integer","title":"Tmdb"}},{"name":"cines","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cines"}},{"name":"lat","in":"query","required":false,"schema":{"type":"number","title":"Lat"}},{"name":"lon","in":"query","required":false,"schema":{"type":"number","title":"Lon"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Film Seances","description":"« Où joue ce film » (19/08, décision Thierry sur la page des sorties) : les séances d'UN\nfilm à travers les grilles des 270 lieux. Deux situations, deux tris :\n  - `cines` (les slugs gardés du client, mode lane salle) → SES cinémas seulement ;\n  - `lat`/`lon` (géoloc AU GESTE, jamais au chargement) → les plus proches d'abord ;\n  - rien → les lieux à la prochaine séance, avec la ville en repère.\nPublic et sans auth comme les pages qu'il sert. Lecture seule, ~270 lignes, l'index GIN\nn'est même pas nécessaire. DÉCLARÉ AVANT /api/film/{item_id} : la paramétrique avalait\n« seances » comme un item_id et répondait 401 — même piège d'ordre que /c/{slug}, et la\npremière version l'a payé au premier test. Les coordonnées viennent de salles_ref par le slug partagé ;\nun lieu déclaré à la main (le Gong) n'y est pas → km nul, il reste listé.","operationId":"film_seances_api_film_seances_get","parameters":[{"name":"tmdb","in":"query","required":true,"schema":{"type":"integer","title":"Tmdb"}},{"name":"cines","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cines"}},{"name":"lat","in":"query","required":false,"schema":{"type":"number","title":"Lat"}},{"name":"lon","in":"query","required":false,"schema":{"type":"number","title":"Lon"}},{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/film/{item_id}":{"get":{"summary":"Get Film","description":"Détails à la demande pour la fiche : durée + langues audio + où regarder (streaming). Connexion Plex\nparesseuse (à l'ouverture de la fiche seulement), mise en cache en mémoire.","operationId":"get_film_api_film__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Film","description":"Détails à la demande pour la fiche : durée + langues audio + où regarder (streaming). Connexion Plex\nparesseuse (à l'ouverture de la fiche seulement), mise en cache en mémoire.","operationId":"get_film_api_film__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/similaires/{item_id}":{"get":{"summary":"Similaires","description":"(27/10, Fiche V2) « Similaires », en bas de la fiche : six œuvres au plus, ouvert sans compte comme la fiche\npublique. D'abord notre moteur (la mémoire des trajectoires, _simil_moteur), puis TMDB en secours pour remplir la\ngrille (_simil_tmdb). Filtrées comme le fil : une bande-annonce prête, rien de ce que la personne a retiré\n(_removed_filter), l'âge du profil de la session par la règle du pin du fil (_recherche_age_ok : la fiche zinema,\nsinon la classification officielle, les œuvres sans âge cachées sous 16 ans en flux). Puis ce qui est sur son\nserveur ou ses services d'abord, le reste ensuite, sans changer l'ordre dans chacun. Jamais l'œuvre elle-même.\nRéponse : {items: [{item_id, title, year, poster, age, kind, summary, genres}], source: moteur | tmdb | aucun,\nprofil} (`kind`, movie ou show, `summary` et `genres`, trois au plus, depuis l'étape 4 : la fiche d'un similaire les\nlit). `source` dit\nd'où vient la liste : « moteur » dès qu'une œuvre vient de notre moteur, « tmdb » si toutes viennent de TMDB.\n`age` : la fiche zinema, sinon la note la plus stricte connue, ou None. `loc`, `sid`, `tz` et `mode` comme la\nloupe (/api/recherche) : la langue des titres et du marché d'âge, le quota d'un appareil, son pays, le mode du fil.\nUne panne de TMDB rend la liste du moteur, ou une liste vide, jamais une erreur.","operationId":"similaires_api_similaires__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Similaires","description":"(27/10, Fiche V2) « Similaires », en bas de la fiche : six œuvres au plus, ouvert sans compte comme la fiche\npublique. D'abord notre moteur (la mémoire des trajectoires, _simil_moteur), puis TMDB en secours pour remplir la\ngrille (_simil_tmdb). Filtrées comme le fil : une bande-annonce prête, rien de ce que la personne a retiré\n(_removed_filter), l'âge du profil de la session par la règle du pin du fil (_recherche_age_ok : la fiche zinema,\nsinon la classification officielle, les œuvres sans âge cachées sous 16 ans en flux). Puis ce qui est sur son\nserveur ou ses services d'abord, le reste ensuite, sans changer l'ordre dans chacun. Jamais l'œuvre elle-même.\nRéponse : {items: [{item_id, title, year, poster, age, kind, summary, genres}], source: moteur | tmdb | aucun,\nprofil} (`kind`, movie ou show, `summary` et `genres`, trois au plus, depuis l'étape 4 : la fiche d'un similaire les\nlit). `source` dit\nd'où vient la liste : « moteur » dès qu'une œuvre vient de notre moteur, « tmdb » si toutes viennent de TMDB.\n`age` : la fiche zinema, sinon la note la plus stricte connue, ou None. `loc`, `sid`, `tz` et `mode` comme la\nloupe (/api/recherche) : la langue des titres et du marché d'âge, le quota d'un appareil, son pays, le mode du fil.\nUne panne de TMDB rend la liste du moteur, ou une liste vide, jamais une erreur.","operationId":"similaires_api_similaires__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist/{item_id}":{"post":{"summary":"Add Watchlist","operationId":"add_watchlist_api_watchlist__item_id__post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Remove Watchlist","operationId":"remove_watchlist_api_watchlist__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/watchlist":{"get":{"summary":"List Watchlist","description":"« À regarder bientôt » : les films gardés (8 plus récents) + dispo (sur une source du compte) +\nwatch_url. On déduit la dispo (re-résolue en direct contre les serveurs si elle n'a pas été\ncapturée à l'ajout), on ne demande rien à l'utilisateur.","operationId":"list_watchlist_api_watchlist_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/plus-tard/tele-dit":{"post":{"summary":"Plus Tard Tele Dit","description":"(02/11, lot « Plus tard sans compte ») Mon zinema a dit « Sur ta télé : ouvre zinema et connecte-toi avec le même\ncompte. » : la marque `pt_tele` du compte s'efface (elle ne se dit qu'une fois, sur le premier écran qui l'a lue).","operationId":"plus_tard_tele_dit_api_plus_tard_tele_dit_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/plus-tard/etat":{"post":{"summary":"Plus Tard Etat","description":"(02/11, lot « Plus tard sans compte ») Les rangées d'une liste Plus tard gardée dans un navigateur sans compte,\npour le bandeau de Mon zinema et « Tout voir ». Corps : {ids (40 au plus, du plus récent au plus ancien), sid, tz,\nlang, region?} ; X-Zn-Flux : l'âge et « avec » du profil que le navigateur montre (hors_age) et ses plateformes\n(Ce soir), jamais l'identifiant d'un profil ni un pseudo. Réponse : la forme de GET /api/watchlist sans `profil`,\n{items, count, cap, ready_count}, les mêmes rangées (_wl_rangee) dans l'ordre reçu, les identifiants inconnus omis ;\n`available` toujours faux et `watch_url` nul (aucun serveur sans compte) ; `ready` et `ready_on` par les plateformes\ndu navigateur dans son pays (la sous-requête `via` de la liste d'un compte, à leurs identifiants) ; `cap` est le\nplafond du navigateur (40), que l'écran ne montre pas.\nLECTURE SEULE : aucune écriture en base (ni _feeditem_for, qui exige un compte, ni _resolve_watch_targets et son\nUPDATE, ni repli TMDB), aucun événement, aucun journal d'identifiant ; le POST garde la liste hors de l'URL (et donc\ndes journaux de Caddy). Quota en mémoire par visiteur (zn_sid et IP) et un filet par IP dix fois plus large, comme\nle cadre des profils. Avec un compte, 400 : sa liste est GET /api/watchlist.","operationId":"plus_tard_etat_api_plus_tard_etat_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ensemble/create":{"post":{"summary":"Ensemble Create","description":"Le host crée une session de match (lien « pour ce soir », 6 h) et s'y inscrit.\n(22/10, profils, 4.6) L'ensemble rend l'intersection avec la liste du principal, sans aucun filtre d'âge : ses\ntrois routes répondent 403 depuis un profil enfant (_refus_profil), ses boutons y sont cachés.","operationId":"ensemble_create_api_ensemble_create_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/ensemble/{sid}/join":{"post":{"summary":"Ensemble Join","description":"Un participant rejoint la session via le lien partagé (si pas expirée).","operationId":"ensemble_join_api_ensemble__sid__join_post","parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","title":"Sid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ensemble/{sid}":{"get":{"summary":"Ensemble Get","description":"Participants (noms→initiales côté front) + INTERSECTION des listes « À regarder bientôt » de tous.\nJamais la liste entière de l'autre — uniquement le commun. Dispo/poster calculés pour le viewer.","operationId":"ensemble_get_api_ensemble__sid__get","parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","title":"Sid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Ensemble Get","description":"Participants (noms→initiales côté front) + INTERSECTION des listes « À regarder bientôt » de tous.\nJamais la liste entière de l'autre — uniquement le commun. Dispo/poster calculés pour le viewer.","operationId":"ensemble_get_api_ensemble__sid__get","parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","title":"Sid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/host/demand":{"get":{"summary":"Host Demand","description":"Vue hébergeur « Ce qu'ils attendent » : films gardés par les membres de tes serveurs (owned)\nqu'AUCUNE de LEURS sources ne couvre = tmdb absent de ta biblio ET de tout AUTRE serveur que CE\nmembre atteint (ex. priam, partagé avec lui). La demande est PAR-MEMBRE : un film visible sur une\nde ses sources n'est pas une demande pour lui (Thierry 27/06). Vérifié EN DIRECT par tmdb —\nindépendant du machine_id capturé, donc règle aussi les items legacy sans localisation. Zéro faux\npositif : item sans tmdb ou biblio injoignable → exclu. Le front anonymise les gros serveurs (≥ HOST_ANON).\n(22/10, profils, 4.6) 403 depuis un profil enfant : la vue rend les films gardés par les membres d'un serveur avec\nleur nom, sans aucun filtre d'âge.","operationId":"host_demand_api_host_demand_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Host Demand","description":"Vue hébergeur « Ce qu'ils attendent » : films gardés par les membres de tes serveurs (owned)\nqu'AUCUNE de LEURS sources ne couvre = tmdb absent de ta biblio ET de tout AUTRE serveur que CE\nmembre atteint (ex. priam, partagé avec lui). La demande est PAR-MEMBRE : un film visible sur une\nde ses sources n'est pas une demande pour lui (Thierry 27/06). Vérifié EN DIRECT par tmdb —\nindépendant du machine_id capturé, donc règle aussi les items legacy sans localisation. Zéro faux\npositif : item sans tmdb ou biblio injoignable → exclu. Le front anonymise les gros serveurs (≥ HOST_ANON).\n(22/10, profils, 4.6) 403 depuis un profil enfant : la vue rend les films gardés par les membres d'un serveur avec\nleur nom, sans aucun filtre d'âge.","operationId":"host_demand_api_host_demand_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/play/{item_id}":{"get":{"summary":"Get Play","description":"URL de lecture (HLS transcodé par le serveur Plex) pour le player natif (app TV).\nFilm → le film ; série → premier épisode non vu (sinon le premier). Renvoie aussi\nrk/machine_id (pour les pings de progression) et viewOffset (reprise).\nItem JELLYFIN : url = la fiche du film dans SON web Jellyfin (défaut honnête),\nfile_url = le stream direct (static=true) → Infuse/VLC via le Lecteur existant.","operationId":"get_play_api_play__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Play","description":"URL de lecture (HLS transcodé par le serveur Plex) pour le player natif (app TV).\nFilm → le film ; série → premier épisode non vu (sinon le premier). Renvoie aussi\nrk/machine_id (pour les pings de progression) et viewOffset (reprise).\nItem JELLYFIN : url = la fiche du film dans SON web Jellyfin (défaut honnête),\nfile_url = le stream direct (static=true) → Infuse/VLC via le Lecteur existant.","operationId":"get_play_api_play__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/clip/{item_id}":{"get":{"summary":"Get Clip","description":"Extrait de découverte (45 s) PRÉ-EXTRAIT en MP4, servi same-origin (fluide comme les curés).\n1re demande → génération en tâche de fond + 'pending' (le client réessaie) ; ensuite → 'ready' +\nURL du MP4 (joué EN BOUCLE ; langue choisie à la lecture car le MP4 est multi-pistes).","operationId":"get_clip_api_clip__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Clip","description":"Extrait de découverte (45 s) PRÉ-EXTRAIT en MP4, servi same-origin (fluide comme les curés).\n1re demande → génération en tâche de fond + 'pending' (le client réessaie) ; ensuite → 'ready' +\nURL du MP4 (joué EN BOUCLE ; langue choisie à la lecture car le MP4 est multi-pistes).","operationId":"get_clip_api_clip__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/clip_src/{item_id}":{"get":{"summary":"Get Clip Src","description":"Source ON-DEVICE de l'extrait (voie VLCKit tvOS) : renvoie l'URL du FICHIER BRUT (part file) du\nserveur de l'user + la fenêtre [start,end] (s) + la langue préférée. RIEN n'est extrait ni stocké\nchez nous — le client (VLCKit) seek le fichier en range et décode on-device (cf. voie C,\nproject_extraits). La langue se choisit à la lecture parmi les pistes du fichier.","operationId":"get_clip_src_api_clip_src__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Clip Src","description":"Source ON-DEVICE de l'extrait (voie VLCKit tvOS) : renvoie l'URL du FICHIER BRUT (part file) du\nserveur de l'user + la fenêtre [start,end] (s) + la langue préférée. RIEN n'est extrait ni stocké\nchez nous — le client (VLCKit) seek le fichier en range et décode on-device (cf. voie C,\nproject_extraits). La langue se choisit à la lecture parmi les pistes du fichier.","operationId":"get_clip_src_api_clip_src__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/play/progress":{"post":{"summary":"Post Play Progress","description":"Progression du player natif → timeline Plex (viewOffset, vu à ~90 %). C'est ce qui\nnourrit le moteur : un visionnage via l'app TV laisse la même trace qu'un visionnage Plex.","operationId":"post_play_progress_api_play_progress_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/events":{"post":{"summary":"Post Events","operationId":"post_events_api_events_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/metrics":{"get":{"summary":"Get Metrics","description":"Les 2 métriques phares + les attributions BA→visionnage (cf. metrics.py).\nRéservé au fondateur : le rapport contient les pseudos + visionnages d'autres utilisateurs.","operationId":"get_metrics_api_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Get Metrics","description":"Les 2 métriques phares + les attributions BA→visionnage (cf. metrics.py).\nRéservé au fondateur : le rapport contient les pseudos + visionnages d'autres utilisateurs.","operationId":"get_metrics_api_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin":{"get":{"summary":"Admin Page","description":"(14/10, lot 3 de la nouvelle admin) /admin = l'accueil du matin : ce qui attend une décision, rangé par échéance,\npuis la santé au rendement et le journal des gestes (décision de Thierry du 14/10 : la décision en tête, l'audience\nà un clic, sur /admin/audience). Avant le 14/10, /admin servait les cinq chiffres de l'audience (16/09).","operationId":"admin_page_admin_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Page","description":"(14/10, lot 3 de la nouvelle admin) /admin = l'accueil du matin : ce qui attend une décision, rangé par échéance,\npuis la santé au rendement et le journal des gestes (décision de Thierry du 14/10 : la décision en tête, l'audience\nà un clic, sur /admin/audience). Avant le 14/10, /admin servait les cinq chiffres de l'audience (16/09).","operationId":"admin_page_admin_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/accueil":{"get":{"summary":"Admin Accueil Api","description":"Les données de l'accueil (admin_accueil). Cache de 60 s lu APRÈS la garde (le défaut corrigé en 03a9458 servait\nun cache avant elle) ; ?frais=1 le recalcule. Lecture seule, comme toutes les lectures de l'admin.","operationId":"admin_accueil_api_api_admin_accueil_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Accueil Api","description":"Les données de l'accueil (admin_accueil). Cache de 60 s lu APRÈS la garde (le défaut corrigé en 03a9458 servait\nun cache avant elle) ; ?frais=1 le recalcule. Lecture seule, comme toutes les lectures de l'admin.","operationId":"admin_accueil_api_api_admin_accueil_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/detail":{"get":{"summary":"Admin Detail Page","description":"L'ancien tableau de bord complet (sessions, QoE, SEO, comptes, campagnes) : le détail derrière les cinq chiffres.","operationId":"admin_detail_page_admin_detail_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Detail Page","description":"L'ancien tableau de bord complet (sessions, QoE, SEO, comptes, campagnes) : le détail derrière les cinq chiffres.","operationId":"admin_detail_page_admin_detail_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/ba":{"get":{"summary":"Admin Ba Page","description":"(14/10, Thierry : « pouvoir faire une recherche sur les BA, les visionner ») Le lot 1 de la nouvelle admin :\nchercher et regarder les BA, en lecture seule (design/admin/etape1/PROPOSITIONS.md, section 3).","operationId":"admin_ba_page_admin_ba_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Ba Page","description":"(14/10, Thierry : « pouvoir faire une recherche sur les BA, les visionner ») Le lot 1 de la nouvelle admin :\nchercher et regarder les BA, en lecture seule (design/admin/etape1/PROPOSITIONS.md, section 3).","operationId":"admin_ba_page_admin_ba_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/ba":{"get":{"summary":"Admin Ba Cherche","description":"La recherche : tmdb, tt…, playback ou URL Mux, URL source, sinon un titre dans toutes ses langues\n(admin_ba.reconnait). Requête vide : les dernières écritures, et les comptes du stock.","operationId":"admin_ba_cherche_api_admin_ba_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Ba Cherche","description":"La recherche : tmdb, tt…, playback ou URL Mux, URL source, sinon un titre dans toutes ses langues\n(admin_ba.reconnait). Requête vide : les dernières écritures, et les comptes du stock.","operationId":"admin_ba_cherche_api_admin_ba_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ba/oeuvre":{"get":{"summary":"Admin Ba Oeuvre","description":"Une œuvre : ses emplacements (et les anciennes vidéos encore lisibles), sa fiche âge, 90 jours d'ops_log.","operationId":"admin_ba_oeuvre_api_admin_ba_oeuvre_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"movie","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Ba Oeuvre","description":"Une œuvre : ses emplacements (et les anciennes vidéos encore lisibles), sa fiche âge, 90 jours d'ops_log.","operationId":"admin_ba_oeuvre_api_admin_ba_oeuvre_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"movie","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ba/vues":{"get":{"summary":"Admin Ba Vues","description":"Les comptes des files (refus NON décidés), le sondage du jour, et le jeton des gestes.","operationId":"admin_ba_vues_api_admin_ba_vues_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Ba Vues","description":"Les comptes des files (refus NON décidés), le sondage du jour, et le jeton des gestes.","operationId":"admin_ba_vues_api_admin_ba_vues_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/ba/file":{"get":{"summary":"Admin Ba File","description":"Une file : ses refus dans l'ordre de revue (non décidés d'abord, ou tous avec toutes=1). v=gestes : les derniers\ngestes. v=jour&jour=AAAA-MM-JJ : tout le lot de refus d'un jour (quand le sondage infirme un refus sûr).","operationId":"admin_ba_file_api_admin_ba_file_get","parameters":[{"name":"v","in":"query","required":false,"schema":{"type":"string","default":"langue","title":"V"}},{"name":"jour","in":"query","required":false,"schema":{"type":"string","default":"","title":"Jour"}},{"name":"toutes","in":"query","required":false,"schema":{"type":"string","default":"","title":"Toutes"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Ba File","description":"Une file : ses refus dans l'ordre de revue (non décidés d'abord, ou tous avec toutes=1). v=gestes : les derniers\ngestes. v=jour&jour=AAAA-MM-JJ : tout le lot de refus d'un jour (quand le sondage infirme un refus sûr).","operationId":"admin_ba_file_api_admin_ba_file_get","parameters":[{"name":"v","in":"query","required":false,"schema":{"type":"string","default":"langue","title":"V"}},{"name":"jour","in":"query","required":false,"schema":{"type":"string","default":"","title":"Jour"}},{"name":"toutes","in":"query","required":false,"schema":{"type":"string","default":"","title":"Toutes"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ba/attention":{"get":{"summary":"Admin Ba Attention Api","description":"(20/10, go de Thierry « pour les bandes seules ») Une bande par BA : où les engagés décrochent, tronçon par\ntronçon de 5 s, et les vues. Période 7, 28 ou 90 jours ; tri par lancements, par tenue à 0:30 ou par vues.","operationId":"admin_ba_attention_api_api_admin_ba_attention_get","parameters":[{"name":"p","in":"query","required":false,"schema":{"type":"string","default":"28","title":"P"}},{"name":"tri","in":"query","required":false,"schema":{"type":"string","default":"lancements","title":"Tri"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Ba Attention Api","description":"(20/10, go de Thierry « pour les bandes seules ») Une bande par BA : où les engagés décrochent, tronçon par\ntronçon de 5 s, et les vues. Période 7, 28 ou 90 jours ; tri par lancements, par tenue à 0:30 ou par vues.","operationId":"admin_ba_attention_api_api_admin_ba_attention_get","parameters":[{"name":"p","in":"query","required":false,"schema":{"type":"string","default":"28","title":"P"}},{"name":"tri","in":"query","required":false,"schema":{"type":"string","default":"lancements","title":"Tri"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/seances":{"get":{"summary":"Admin Seances Page","description":"La vue « Séances » (go de Thierry du 14/10) : une ligne par visite au sens d'AUDIENCE.md, filtres par salle,\ncompte, pays, surface et action, la chronologie lisible d'une séance. Gatée comme toutes les pages admin.","operationId":"admin_seances_page_admin_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Seances Page","description":"La vue « Séances » (go de Thierry du 14/10) : une ligne par visite au sens d'AUDIENCE.md, filtres par salle,\ncompte, pays, surface et action, la chronologie lisible d'une séance. Gatée comme toutes les pages admin.","operationId":"admin_seances_page_admin_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/seances":{"get":{"summary":"Admin Seances Api","description":"Les séances d'une période (1, 7 ou 28 jours, ou deux dates), filtrées, avec les facettes de la période et le\nrésumé de la sélection (les chiffres de l'offre Vitrine quand le filtre est une salle). Lecture seule, garde\navant tout calcul, aucun cache. Admins et tests exclus sauf tous=1.","operationId":"admin_seances_api_api_admin_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Seances Api","description":"Les séances d'une période (1, 7 ou 28 jours, ou deux dates), filtrées, avec les facettes de la période et le\nrésumé de la sélection (les chiffres de l'offre Vitrine quand le filtre est une salle). Lecture seule, garde\navant tout calcul, aucun cache. Admins et tests exclus sauf tous=1.","operationId":"admin_seances_api_api_admin_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/seances/{sid}":{"get":{"summary":"Admin Seance Api","description":"Une séance : sa chronologie lisible, à partir de son premier événement (sid = events.id).","operationId":"admin_seance_api_api_admin_seances__sid__get","parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","title":"Sid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Seance Api","description":"Une séance : sa chronologie lisible, à partir de son premier événement (sid = events.id).","operationId":"admin_seance_api_api_admin_seances__sid__get","parameters":[{"name":"sid","in":"path","required":true,"schema":{"type":"string","title":"Sid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ba/candidates":{"get":{"summary":"Admin Ba Candidates","description":"Les vidéos candidates d'un emplacement (admin_acquisition.candidates), avec leur exécutant, leur prix et leur\nrefus éventuel. La base est lue et la transaction FERMÉE avant les appels réseau (TMDB, KinoCheck, Dailymotion,\nWikidata, AlloCiné : gratuits, bornés), règle du 13/10.","operationId":"admin_ba_candidates_api_admin_ba_candidates_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"movie","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Ba Candidates","description":"Les vidéos candidates d'un emplacement (admin_acquisition.candidates), avec leur exécutant, leur prix et leur\nrefus éventuel. La base est lue et la transaction FERMÉE avant les appels réseau (TMDB, KinoCheck, Dailymotion,\nWikidata, AlloCiné : gratuits, bornés), règle du 13/10.","operationId":"admin_ba_candidates_api_admin_ba_candidates_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"movie","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/demandes":{"get":{"summary":"Admin Demandes","description":"La file des demandes (les ouvertes d'abord) et ses comptes, par exécutant.","operationId":"admin_demandes_api_admin_demandes_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Admin Demandes","description":"La file des demandes (les ouvertes d'abord) et ses comptes, par exécutant.","operationId":"admin_demandes_api_admin_demandes_get","parameters":[{"name":"kind","in":"query","required":false,"schema":{"type":"string","default":"","title":"Kind"}},{"name":"tmdb","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tmdb"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Admin Demande Creer","description":"Une demande : remplacer par une candidate (Dailymotion ou AlloCiné au serveur, YouTube au Mac), écouter ou lire\nles sous-titres au Mac. Le prix a été affiché avant le geste ; il est rendu avec la demande.","operationId":"admin_demande_creer_api_admin_demandes_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/demandes/{did}/annuler":{"post":{"summary":"Admin Demande Annuler","description":"Annule une demande qui attend encore (une demande prise par un exécutant va à son terme).","operationId":"admin_demande_annuler_api_admin_demandes__did__annuler_post","parameters":[{"name":"did","in":"path","required":true,"schema":{"type":"string","title":"Did"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ba/sorties":{"get":{"summary":"Admin Ba Sorties","description":"Les sorties de la semaine : le dernier relevé de couverture_sorties.py par marché (ops_log couverture_ba), avec\nles demandes ouvertes sur leurs créneaux.","operationId":"admin_ba_sorties_api_admin_ba_sorties_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Ba Sorties","description":"Les sorties de la semaine : le dernier relevé de couverture_sorties.py par marché (ops_log couverture_ba), avec\nles demandes ouvertes sur leurs créneaux.","operationId":"admin_ba_sorties_api_admin_ba_sorties_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/ba/{geste}":{"post":{"summary":"Admin Ba Geste","description":"Un geste sur une ligne de trailer_hls (admin_ba_gestes) : lever, confirmer, retirer, valider, annuler.\nOrdre des gardes : la session, l'origine de la requête, le jeton anti-CSRF, le droit admin ; puis, pour lever et valider,\nla vie de la vidéo chez Mux, interrogée HORS transaction (règle du 13/10) ; enfin une transaction courte qui\nverrouille la ligne (lock_timeout 3 s), vérifie qu'elle est celle qui a été affichée, écrit et journalise.","operationId":"admin_ba_geste_api_admin_ba__geste__post","parameters":[{"name":"geste","in":"path","required":true,"schema":{"type":"string","title":"Geste"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/users":{"get":{"summary":"Admin Users","description":"Liste des utilisateurs (demande Thierry 08/07) : qui, arrivée, d'où (tz du feed — le pays réel\nviendra de watch_region avec la brique sources ③), langues (prefs audio/ST = proxy de la langue\nd'interface, qui vit côté client), taille Plex, plateformes vues, sessions, dernière activité.","operationId":"admin_users_api_admin_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Users","description":"Liste des utilisateurs (demande Thierry 08/07) : qui, arrivée, d'où (tz du feed — le pays réel\nviendra de watch_region avec la brique sources ③), langues (prefs audio/ST = proxy de la langue\nd'interface, qui vit côté client), taille Plex, plateformes vues, sessions, dernière activité.","operationId":"admin_users_api_admin_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users-detail":{"get":{"summary":"Admin Users Detail","description":"Une ligne par user, tout ce qui se calcule. 125 users → une seule réponse, le tri et le\nfiltre vivent dans le navigateur (pas de pagination tant qu'on n'a pas quelques milliers de\nlignes : ça épargne la moitié du code et rend le tableau instantané).","operationId":"admin_users_detail_api_admin_users_detail_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Users Detail","description":"Une ligne par user, tout ce qui se calcule. 125 users → une seule réponse, le tri et le\nfiltre vivent dans le navigateur (pas de pagination tant qu'on n'a pas quelques milliers de\nlignes : ça épargne la moitié du code et rend le tableau instantané).","operationId":"admin_users_detail_api_admin_users_detail_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/users":{"get":{"summary":"Admin Users Page","description":"La seconde admin. Même garde que /admin : fichier HORS static/, servi seulement à un admin,\n404 sinon (on ne révèle pas son existence).","operationId":"admin_users_page_admin_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Users Page","description":"La seconde admin. Même garde que /admin : fichier HORS static/, servi seulement à un admin,\n404 sinon (on ne révèle pas son existence).","operationId":"admin_users_page_admin_users_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/seo":{"get":{"summary":"Admin Seo Api","operationId":"admin_seo_api_api_admin_seo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Seo Api","operationId":"admin_seo_api_api_admin_seo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/audience":{"get":{"summary":"Admin Audience Page","description":"(16/09, Thierry : « des chiffres clairs, qu'on puisse voir la progression ») La page d'audience :\nune seule définition (AUDIENCE.md, imprimée au bas de la page), la courbe mensuelle, les portes.\nMême garde que /admin : fichier HORS static/, 404 sans droit admin.","operationId":"admin_audience_page_admin_audience_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Audience Page","description":"(16/09, Thierry : « des chiffres clairs, qu'on puisse voir la progression ») La page d'audience :\nune seule définition (AUDIENCE.md, imprimée au bas de la page), la courbe mensuelle, les portes.\nMême garde que /admin : fichier HORS static/, 404 sans droit admin.","operationId":"admin_audience_page_admin_audience_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/seo":{"get":{"summary":"Admin Seo Page","description":"Même garde que /admin et /admin/users : fichier HORS static/, 404 sans droit admin.","operationId":"admin_seo_page_admin_seo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Seo Page","description":"Même garde que /admin et /admin/users : fichier HORS static/, 404 sans droit admin.","operationId":"admin_seo_page_admin_seo_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/audience":{"get":{"summary":"Admin Audience","description":"L'audience : actifs par jour et par mois, comptes et anonymes, plus les lectures d'agents IA.\n\nLa série vient de la table audience_jour (écrite chaque nuit, cf. migrate_audience.sql) ; le\nJOUR EN COURS est calculé en direct par le MÊME code que le cron (audience_jour.compter), sinon\nle tableau de bord montrerait hier en prétendant montrer aujourd'hui. Cache 10 min : la page est\nlue par une personne, et le calcul du jour balaie events. `?frais=1` force.\n\n⚖️ Les trois étages ne s'additionnent pas : un compte est une personne, un anonyme est un\nnavigateur, un agent IA est un nombre de LECTURES. Le front doit les afficher séparés.","operationId":"admin_audience_api_admin_audience_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Audience","description":"L'audience : actifs par jour et par mois, comptes et anonymes, plus les lectures d'agents IA.\n\nLa série vient de la table audience_jour (écrite chaque nuit, cf. migrate_audience.sql) ; le\nJOUR EN COURS est calculé en direct par le MÊME code que le cron (audience_jour.compter), sinon\nle tableau de bord montrerait hier en prétendant montrer aujourd'hui. Cache 10 min : la page est\nlue par une personne, et le calcul du jour balaie events. `?frais=1` force.\n\n⚖️ Les trois étages ne s'additionnent pas : un compte est une personne, un anonyme est un\nnavigateur, un agent IA est un nombre de LECTURES. Le front doit les afficher séparés.","operationId":"admin_audience_api_admin_audience_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/overview":{"get":{"summary":"Admin Overview","description":"Données du tableau de bord admin : coup d'œil + santé + croissance + cœur + supply. Réutilise\nmetrics.compute / feed_retention.compute (résilient) + agrégats légers. Admin only (users.is_admin).","operationId":"admin_overview_api_admin_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Overview","description":"Données du tableau de bord admin : coup d'œil + santé + croissance + cœur + supply. Réutilise\nmetrics.compute / feed_retention.compute (résilient) + agrégats légers. Admin only (users.is_admin).","operationId":"admin_overview_api_admin_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/overview-deep":{"get":{"summary":"Admin Overview Deep","description":"Panneaux LOURDS du dashboard, séparés de /overview (train 15/07) pour un premier rendu\ninstantané : couverture univers (~0,8 s), QoE/géo Mux (appels réseau sortants), engagement-\nrendement, part du visionnage (+ lentille fondateur). Le front les charge en différé\n(renderDeep → #deep-slot) ; mêmes blocs qu'avant, déplacés verbatim. Admin only.","operationId":"admin_overview_deep_api_admin_overview_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Overview Deep","description":"Panneaux LOURDS du dashboard, séparés de /overview (train 15/07) pour un premier rendu\ninstantané : couverture univers (~0,8 s), QoE/géo Mux (appels réseau sortants), engagement-\nrendement, part du visionnage (+ lentille fondateur). Le front les charge en différé\n(renderDeep → #deep-slot) ; mêmes blocs qu'avant, déplacés verbatim. Admin only.","operationId":"admin_overview_deep_api_admin_overview_deep_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/yt/recherche":{"get":{"summary":"Yt Recherche","description":"Réglages › Sources › YouTube : chercher une chaîne (nom ou lien collé). Chaque résultat dit son\nétat POUR CET USER : active (s'ajoute d'un clic), demandée, nouvelle (se demande), refusée.\nToute recherche est journalisée (yt_recherches), même sans clic : la demande qui ne se déclare pas.","operationId":"yt_recherche_api_yt_recherche_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/yt/chaine":{"post":{"summary":"Yt Chaine","description":"{channel_id, action: ajouter | retirer}. Ajouter une chaîne ACTIVE = la déclarer (prefs.yt_chaines,\nle pool se reconstruit) ; ajouter une chaîne qui ne l'est pas = la DEMANDER (yt_demandes), ce qui\nvaudra déclaration le jour où on l'active. Retirer efface les deux.","operationId":"yt_chaine_api_yt_chaine_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/yt/mes":{"get":{"summary":"Yt Mes","description":"Mes chaînes (déclarées) et mes demandes en attente, pour la liste du rayon YouTube.","operationId":"yt_mes_api_yt_mes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Yt Mes","description":"Mes chaînes (déclarées) et mes demandes en attente, pour la liste du rayon YouTube.","operationId":"yt_mes_api_yt_mes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/sources":{"get":{"summary":"Get Sources","operationId":"get_sources_api_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Get Sources","operationId":"get_sources_api_sources_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/streaming/sources":{"get":{"summary":"Streaming Sources","description":"Sources streaming cochables (chantier sources ②, 08/07) : les paires ACTIVES de catalog_pairs\npour la région de l'user. « Pour toi » = l'admissible des sources cochées (union pure) — la\nbranche univers du feed (③) lira prefs.sources_streaming ; ici on capte la déclaration.\nRégion : ?region= explicite > prefs.watch_region > langue/tz (fr → FR, sinon US).","operationId":"streaming_sources_api_streaming_sources_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"type":"string","title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Streaming Sources","description":"Sources streaming cochables (chantier sources ②, 08/07) : les paires ACTIVES de catalog_pairs\npour la région de l'user. « Pour toi » = l'admissible des sources cochées (union pure) — la\nbranche univers du feed (③) lira prefs.sources_streaming ; ici on capte la déclaration.\nRégion : ?region= explicite > prefs.watch_region > langue/tz (fr → FR, sinon US).","operationId":"streaming_sources_api_streaming_sources_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"type":"string","title":"Region"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plateformes":{"get":{"summary":"Plateformes Publiques","description":"(09/10, refonte des réglages, D4 et 2.5 point 4) La liste des plateformes d'un pays, SANS COMPTE : la page\nPlateformes de l'appareil (web et iPhone) les coche sur l'appareil et les envoie dans X-Zn-Flux. Route publique,\nsans 401 ni champ de compte, plafonnée par IP (120 par heure, le patron de /api/film-public).\n\nPays : ?region= s'il est suivi, sinon le pays de l'appareil (_region_anonyme : fuseau, région de la locale,\nAccept-Language), sinon le repli de /api/streaming/sources (France si la langue est le français, sinon les\nÉtats-Unis). `repli` dit que le pays de l'appareil est connu et que zinema n'en suit pas encore les plateformes\n(la note de la page, maquette 09-c). Rend {region, regions, repli, providers: [{id, name, logo}]}, `name` étant\nle nom AFFICHÉ dans la langue `lang`.","operationId":"plateformes_publiques_api_plateformes_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"type":"string","default":"","title":"Region"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Plateformes Publiques","description":"(09/10, refonte des réglages, D4 et 2.5 point 4) La liste des plateformes d'un pays, SANS COMPTE : la page\nPlateformes de l'appareil (web et iPhone) les coche sur l'appareil et les envoie dans X-Zn-Flux. Route publique,\nsans 401 ni champ de compte, plafonnée par IP (120 par heure, le patron de /api/film-public).\n\nPays : ?region= s'il est suivi, sinon le pays de l'appareil (_region_anonyme : fuseau, région de la locale,\nAccept-Language), sinon le repli de /api/streaming/sources (France si la langue est le français, sinon les\nÉtats-Unis). `repli` dit que le pays de l'appareil est connu et que zinema n'en suit pas encore les plateformes\n(la note de la page, maquette 09-c). Rend {region, regions, repli, providers: [{id, name, logo}]}, `name` étant\nle nom AFFICHÉ dans la langue `lang`.","operationId":"plateformes_publiques_api_plateformes_get","parameters":[{"name":"region","in":"query","required":false,"schema":{"type":"string","default":"","title":"Region"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/letterboxd/import":{"post":{"summary":"Letterboxd Import","description":"Reçoit le ZIP d'export (ou un seul CSV) dans le CORPS de la requête, pas en multipart :\nça évite la dépendance python-multipart pour un octet-stream qu'on lit d'un bloc.\n\nTout est synchrone parce que c'est devenu rapide : l'appariement fait DEUX requêtes pour tout le\nlot, soit ~1 ms par film (339 ms avant réécriture — une requête par titre sur une table sans\nindex de titre). Mille films tiennent en une seconde, un job de fond serait de la cérémonie.","operationId":"letterboxd_import_api_letterboxd_import_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/jellyfin/connect":{"post":{"summary":"Jellyfin Connect","operationId":"jellyfin_connect_api_jellyfin_connect_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/jellyfin/{res_id}":{"delete":{"summary":"Jellyfin Delete","operationId":"jellyfin_delete_api_jellyfin__res_id__delete","parameters":[{"name":"res_id","in":"path","required":true,"schema":{"type":"string","title":"Res Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plex/{res_id}/unlink":{"post":{"summary":"Plex Unlink","operationId":"plex_unlink_api_plex__res_id__unlink_post","parameters":[{"name":"res_id","in":"path","required":true,"schema":{"type":"string","title":"Res Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plex/{res_id}/relink":{"post":{"summary":"Plex Relink","description":"Re-lier un serveur délié : 'active' + ré-ingest de fond avec le token de COMPTE (celui de\nla ressource a été jeté au déliage) — la découverte re-remplit base_uri/token/server_items.","operationId":"plex_relink_api_plex__res_id__relink_post","parameters":[{"name":"res_id","in":"path","required":true,"schema":{"type":"string","title":"Res Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/plex/resync":{"post":{"summary":"Plex Resync","description":"« Resynchroniser maintenant » — compte ENTIER (le bouton est par serveur mais l'ingest\nincrémental rend les serveurs frais quasi gratuits ; le per-serveur strict n'en vaut pas le\nrefactor). Rate-limité : le cron horaire reste le régime normal.","operationId":"plex_resync_api_plex_resync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/plex/{res_id}/alias":{"post":{"summary":"Plex Alias","description":"Alias zinema-ONLY (multi-Plex invité : « le serveur de Kevin ») — on ne renomme JAMAIS le\nserveur Plex lui-même. Vide = retour au nom réel. Affiché partout côté client (Sources, CTA\ndu feed via COALESCE(alias, server_name)).","operationId":"plex_alias_api_plex__res_id__alias_post","parameters":[{"name":"res_id","in":"path","required":true,"schema":{"type":"string","title":"Res Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/removed/{item_id}":{"post":{"summary":"Removed Add","operationId":"removed_add_api_removed__item_id__post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Removed Restore","description":"« Remettre » (Retirés de ton flux) et « Annuler » du bandeau de retrait : la ligne de feed_removed s'efface.\n(09/10, refonte des réglages, section 6) Un event serveur `feed_restore {from}` le dit désormais (avant : aucune\ntrace), sur le modèle de deja_vu_annule : `from` = annuler | reglages, passé par le client (`?from=`), absent\npour un client d'avant. Seulement quand une ligne est vraiment remise ; son échec ne défait jamais la remise.\n(22/10, profils, 4.6) 403 depuis un profil enfant, comme le retrait. Le droit se juge avant tout effet, hors du\ntry de l'événement : des profils illisibles font échouer la remise (on ne sait plus si c'est un enfant, 4.3) ;\nau principal, rien n'est lu.","operationId":"removed_restore_api_removed__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deja-vu/{item_id}":{"post":{"summary":"Deja Vu Add","description":"(19/09, go Thierry) « Déjà vu » sur la carte : ce qu'on a vu avant Plex ou hors Plex sort de la mémoire du\ndéjà-vu comme si Plex l'avait dit, pour le feed comme pour les pages « nouveau sur ». Un event déclaré (INSERT only),\nla mémoire STATE mise à jour sur-le-champ : la carte suivante ne le resservira pas. Compte requis : la mémoire\nest celle d'un compte ; l'anonyme n'a que la session.","operationId":"deja_vu_add_api_deja_vu__item_id__post","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Deja Vu Annule","description":"« Annuler » du toast : un event d'annulation (jamais de DELETE dans events), la mémoire STATE rendue.","operationId":"deja_vu_annule_api_deja_vu__item_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/removed":{"get":{"summary":"Removed List","description":"La liste associée (Réglages) — payload EXPORT-READY : tmdb_id en première classe pour\nl'outil tiers du owner qui automatise SA suppression (rail à la Seerr, en miroir côté\nrejet : zinema PUBLIE le signal, l'outil agit, on ne supprime jamais un fichier).\nAuth : session (écran + export manuel) OU jeton script ?token= (le cron du owner).","operationId":"removed_list_api_removed_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Removed List","description":"La liste associée (Réglages) — payload EXPORT-READY : tmdb_id en première classe pour\nl'outil tiers du owner qui automatise SA suppression (rail à la Seerr, en miroir côté\nrejet : zinema PUBLIE le signal, l'outil agit, on ne supprime jamais un fichier).\nAuth : session (écran + export manuel) OU jeton script ?token= (le cron du owner).","operationId":"removed_list_api_removed_get","parameters":[{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/token/removed":{"get":{"summary":"Token Removed Get","description":"État du jeton script (sous-page BA retirées). owner=False → l'UI n'affiche rien.","operationId":"token_removed_get_api_token_removed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Token Removed Gen","description":"Génère (ou RÉGÉNÈRE = révoque l'ancien) le jeton lecture seule du scope 'removed'.","operationId":"token_removed_gen_api_token_removed_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"Token Removed Revoke","operationId":"token_removed_revoke_api_token_removed_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Token Removed Get","description":"État du jeton script (sous-page BA retirées). owner=False → l'UI n'affiche rien.","operationId":"token_removed_get_api_token_removed_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/token/tautulli":{"get":{"summary":"Token Tautulli Get","description":"État du jeton webhook (sous-page Sources). owner=False → l'UI n'affiche rien.","operationId":"token_tautulli_get_api_token_tautulli_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Token Tautulli Gen","description":"Génère (ou RÉGÉNÈRE) le jeton webhook. Régénérer = l'agent Tautulli configuré avec\nl'ancien s'arrête de porter (403) : l'UI n'offre que Générer/Révoquer, pas de regen silencieux.","operationId":"token_tautulli_gen_api_token_tautulli_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"delete":{"summary":"Token Tautulli Revoke","operationId":"token_tautulli_revoke_api_token_tautulli_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Token Tautulli Get","description":"État du jeton webhook (sous-page Sources). owner=False → l'UI n'affiche rien.","operationId":"token_tautulli_get_api_token_tautulli_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/push/register":{"post":{"summary":"Push Register","description":"Le client enregistre son token APNs (à l'autorisation, puis à chaque lancement autorisé).\nUpsert par token : un téléphone qui change de compte suit son user. env = sandbox (build\nXcode) ou prod (TestFlight/App Store) — APNs a deux passerelles.","operationId":"push_register_api_push_register_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/prefs":{"get":{"summary":"Get Prefs","operationId":"get_prefs_api_prefs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Set Prefs","operationId":"set_prefs_api_prefs_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Get Prefs","operationId":"get_prefs_api_prefs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/profils/cadre":{"get":{"summary":"Profils Cadre","description":"Le cadre des profils (4.4), sans compte aussi : {on, exemples}. `on` dit si CE compte a l'interface\n(_profils_ouverts : le niveau du drapeau, administrateur, testeur) ; sans compte, profils_anon (et `test=1` en mode\ntest). Quota par visiteur, comme /api/recherche : le compte, sinon zn_sid (`sid`) et IP, et un filet par IP seule\ndix fois plus large, jamais l'IP seule (les IP mobiles partagées). Le client garde les exemples pour la journée et\nla langue ; un cadre illisible ou en 429 ne vaut pas `off`, le client garde le dernier état connu. (22/10, sans\nplafond) Plus de `max` : aucun nombre de profils n'est une limite, l'écran ne dit jamais « N profils au plus ».","operationId":"profils_cadre_api_profils_cadre_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"test","in":"query","required":false,"schema":{"type":"string","default":"","title":"Test"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Profils Cadre","description":"Le cadre des profils (4.4), sans compte aussi : {on, exemples}. `on` dit si CE compte a l'interface\n(_profils_ouverts : le niveau du drapeau, administrateur, testeur) ; sans compte, profils_anon (et `test=1` en mode\ntest). Quota par visiteur, comme /api/recherche : le compte, sinon zn_sid (`sid`) et IP, et un filet par IP seule\ndix fois plus large, jamais l'IP seule (les IP mobiles partagées). Le client garde les exemples pour la journée et\nla langue ; un cadre illisible ou en 429 ne vaut pas `off`, le client garde le dernier état connu. (22/10, sans\nplafond) Plus de `max` : aucun nombre de profils n'est une limite, l'écran ne dit jamais « N profils au plus ».","operationId":"profils_cadre_api_profils_cadre_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"test","in":"query","required":false,"schema":{"type":"string","default":"","title":"Test"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/profils":{"get":{"summary":"Profils Liste","description":"Les profils du compte (4.4) : {actif, enfant_actif, profils: [{id, pseudo, couleur, avatar, age, avec, enfant,\nfilms, ailleurs}]}, le principal d'abord (id null ; sans choix, tête 0 et couleur 0, 23/10). `films` : les\nlignes vivantes de sa liste Plus tard ; `ailleurs` : les AUTRES appareils posés sur ce profil et vus sur 30 jours\n(la confirmation de la suppression, 5.5). Ouverte quel que soit le drapeau (tranché par le plan) : une session\nposée sur un profil en a besoin pour l'interface minimale.\n(22/10, deuxième relecture du socle) Sans plafond, la réponse grandit avec le foyer : un quota par compte, celui du\ncadre (120 par heure), qu'aucune famille n'atteint ; en 429, le client garde la dernière liste connue, comme pour le\ncadre.","operationId":"profils_liste_api_profils_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Profils Cree","description":"Crée un profil ajouté (4.4) : {pseudo, couleur?, avatar?, age: null|3..17, avec} ; rend le profil. Les six clés\nde ses filtres sont toutes écrites (2.2, l'invariant : un profil n'hérite jamais de l'âge du principal) : l'âge par\n_prefs_pose_age, même Sans limite (max_age_num null, max_age_flux posé ou retiré par lui), « avec », puis le type,\nQuoi et « Uniquement mes sources » copiés du principal, valeurs nulles comprises. Le verrou de la ligne du compte\nest pris AVANT de lire les couleurs du foyer : deux téléphones qui créent ensemble prennent deux couleurs libres,\npas la même (8.4). Aucun plafond de nombre (décision de Thierry du 22/10) : seuls les 20 essais par heure et par\ncompte (429) arrêtent un robot. Elle ne change pas la session : le client appelle /actif (tranché par le plan).\n(23/10, l'avatar) Sans `couleur` (ou hors de 0 à 5), la suivante libre par la règle des jumelles (_couleur_libre,\ncelle du principal comptée) ; puis sans `avatar` (ou hors de la liste), une tête tirée par _avatar_libre pour cette\ncouleur, parmi celles que personne ne porte au foyer, principal compris. Choisies d'office, jamais obligatoires.","operationId":"profils_cree_api_profils_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Profils Liste","description":"Les profils du compte (4.4) : {actif, enfant_actif, profils: [{id, pseudo, couleur, avatar, age, avec, enfant,\nfilms, ailleurs}]}, le principal d'abord (id null ; sans choix, tête 0 et couleur 0, 23/10). `films` : les\nlignes vivantes de sa liste Plus tard ; `ailleurs` : les AUTRES appareils posés sur ce profil et vus sur 30 jours\n(la confirmation de la suppression, 5.5). Ouverte quel que soit le drapeau (tranché par le plan) : une session\nposée sur un profil en a besoin pour l'interface minimale.\n(22/10, deuxième relecture du socle) Sans plafond, la réponse grandit avec le foyer : un quota par compte, celui du\ncadre (120 par heure), qu'aucune famille n'atteint ; en 429, le client garde la dernière liste connue, comme pour le\ncadre.","operationId":"profils_liste_api_profils_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/profils/modifier":{"post":{"summary":"Profils Modifie","description":"Modifie un profil (4.4) : {id, pseudo?, couleur?, avatar?, age?, avec?}, seulement les champs touchés ; rend le\nprofil. `id` null : le principal, sous le verrou de sa ligne de users : profil_pseudo (facultatif : null ou vide le\nretire), profil_couleur, profil_avatar, et l'âge par _prefs_pose_age, 0 (« Tous publics ») accepté seulement là ;\nun pseudo enregistré seul ne touche pas l'âge, et 0 ne devient jamais 3 sans que personne l'ait voulu (5.5). Un\nprofil ajouté : sa ligne lue FOR UPDATE puis réécrite, pseudo obligatoire, âge null ou de 3 à 17.\n(23/10, « La tête d'abord », décision de Thierry sur la recommandation de l'avatar) Le principal (`id` null) se\nmodifie QUEL QUE SOIT LE DRAPEAU, pour tout compte : sa tête, sa couleur et son pseudo partent avant les profils,\nsans aucune donnée d'enfant. Jamais depuis une session posée sur un profil ajouté quand le drapeau est fermé pour\nce compte (403 profil_ajoute, profil_enfant sous un enfant, _refus_principal) : une session restée sur Tom après la\ncoupure ne réécrit pas le principal. Drapeau ouvert, la règle de 4.6 (un enfant 403, un adulte ajouté permis).\nUn profil ajouté (`id` uuid) : le drapeau d'abord (404 fermé), comme avant. `avatar` : 0 à 23, sinon 400.","operationId":"profils_modifie_api_profils_modifier_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/profils/supprimer":{"post":{"summary":"Profils Supprime","description":"Supprime un profil ajouté (4.4, 8.3), jamais le principal (400 : c'est le compte). Dans une transaction, sous le\nverrou de sa ligne : ses films vivants et ses événements (user_id et payload->>'pf') comptés, les événements\neffacés puis recomptés à 0 (règle « Suppression vérifiée seulement » : gardés détachés, ils reviendraient au\nprincipal et mêleraient après coup les gestes d'un enfant au fil de son parent), la ligne effacée : sa liste part\nen cascade, et ses sessions reviennent au principal (ON DELETE SET NULL), qui le voient à leur réponse suivante\n(profil.id null). Rend {ok, films, evenements}.\n(01/11, garde-fou) Un profil ENFANT n'a aucun événement à son uuid : les siens portent le marqueur PF_ENFANT, ne disent\npas quel enfant et restent au compte jusqu'à sa suppression (la politique de confidentialité le dit) ; `evenements`\nvaut alors 0.","operationId":"profils_supprime_api_profils_supprimer_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/profils/actif":{"post":{"summary":"Profils Actif","description":"Pose le profil de CE jeton, et de lui seul (2.5, 4.4) : {id: null|uuid}. L'UPDATE vérifie, dans la même requête,\nque le profil est du compte (8.4). Un id inconnu, mal formé ou d'un autre compte : la session revient au principal\net la réponse le dit (`inconnu: true`), jamais un 401. Drapeau fermé : seulement le retour au principal (404 pour\nun id), qui ramène une session restée sur un profil (5.10). Changer de profil est permis à tous (4.6). Rend\n{actif, profil: {id, pseudo, couleur, avatar, age, avec, enfant}, inconnu?}.","operationId":"profils_actif_api_profils_actif_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/profils/oublier-local":{"post":{"summary":"Profils Oublier Local","description":"Sans compte (8.3) : efface les événements de ce zn_sid (`sid`, user_id NULL) qui portent ce `pf`, comptés avant,\nrecomptés à 0 après ; {ok, evenements}. Appelée par le navigateur quand il supprime un de ses profils. Sans\ndrapeau (tranché par le plan) : elle n'efface que ce que le navigateur a lui-même marqué. Quota par IP.","operationId":"profils_oublier_local_api_profils_oublier_local_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/unsub":{"get":{"summary":"Unsub Page","operationId":"unsub_page_api_unsub_get","parameters":[{"name":"u","in":"query","required":false,"schema":{"type":"string","default":"","title":"U"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Unsub Page","operationId":"unsub_page_api_unsub_get","parameters":[{"name":"u","in":"query","required":false,"schema":{"type":"string","default":"","title":"U"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Unsub Do","operationId":"unsub_do_api_unsub_post","parameters":[{"name":"u","in":"query","required":false,"schema":{"type":"string","default":"","title":"U"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scan":{"post":{"summary":"Scan Proxy","description":"Reconnaissance d'affiches (GO 11/08) — proxy vers le conteneur zn-scan (onnx CLIP +\nRapidOCR + index 59k affiches, voir scanbench/). Body = l'image BRUTE ; query texte (l'OCR du client iOS — VisionKit lit les typos\ndécoratives que l'OCR serveur rate), debug. PUBLIC : le scan doit marcher avant tout\ncompte (le geste viral) ; l'anti-abus = la taille max côté zn-scan et le rythme\nnaturel du geste. zn-scan écrit lui-même l'event `scan` en DB.","operationId":"scan_proxy_api_scan_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/topshelf":{"get":{"summary":"Topshelf","description":"Vitrine Top Shelf tvOS (carrousel « Les trois portes »), PUBLIQUE (l'extension tvOS n'a pas de\nsession). Quelques films populaires (cold-start) + backdrop TMDB 16:9 + titre. Caché (TTL).\n(11/10, Apple TV 1.14, lot S, D3 et 2.16) `lang` : les titres dans la langue de la télé (sans `lang`, ceux\nd'avant) ; titres tous publics seulement, courts métrages retirés (_topshelf_items) ; `backdrop_2x` (l'image\n`original`) et sa largeur `backdrop_2x_largeur`, `backdrop` restant le w1280 que lisent les télés installées.\nUn cache par langue.","operationId":"topshelf_api_topshelf_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Topshelf","description":"Vitrine Top Shelf tvOS (carrousel « Les trois portes »), PUBLIQUE (l'extension tvOS n'a pas de\nsession). Quelques films populaires (cold-start) + backdrop TMDB 16:9 + titre. Caché (TTL).\n(11/10, Apple TV 1.14, lot S, D3 et 2.16) `lang` : les titres dans la langue de la télé (sans `lang`, ceux\nd'avant) ; titres tous publics seulement, courts métrages retirés (_topshelf_items) ; `backdrop_2x` (l'image\n`original`) et sa largeur `backdrop_2x_largeur`, `backdrop` restant le w1280 que lisent les télés installées.\nUn cache par langue.","operationId":"topshelf_api_topshelf_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/health":{"get":{"summary":"Health","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Health","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Root","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/share":{"post":{"summary":"Share Create","operationId":"share_create_api_share_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/qr/court":{"post":{"summary":"Qr Court Cree","description":"Le code court d'une séance, pour le QR « Réserver sur ton téléphone » de la télé : {code, url}. `seance` est la\nforme longue de /q/ (« {slug}.{tmdb}.{AAAAMMJJHHMM}[.{v}] », l'heure comprise). Sans compte ; débit limité par\nadresse comme les autres routes publiques. Illisible : 400 ; séance absente de la grille du cinéma : 404 ; base\nsans réponse : 503. Dans les trois cas, la télé affiche la forme longue.","operationId":"qr_court_cree_api_qr_court_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/q/{code}":{"get":{"summary":"Qr Renvoi","description":"Le renvoi d'un QR de réservation de l'Apple TV, code court (_QR_COURT) ou forme longue (_QR_CODE) : journalise\nqr_scan, puis 302 vers la billetterie de la séance, sinon la page du cinéma.","operationId":"qr_renvoi_q__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/b/{code}":{"get":{"summary":"Share Page","operationId":"share_page_b__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Share Page","operationId":"share_page_b__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subs/{kind}/{tmdb}/{lang}.vtt":{"get":{"summary":"Subs Vtt","description":"VTT d'une BA (sous-titres MVP 21/07). Public : contenu de BA publique, la page /b/\nanonyme le consommera aussi. Le client tente sa langue et tolère le 404 (fetch lazy,\npas de champ dans le payload feed → zéro toucher au feed-builder).","operationId":"subs_vtt_api_subs__kind___tmdb___lang__vtt_get","parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","title":"Kind"}},{"name":"tmdb","in":"path","required":true,"schema":{"type":"integer","title":"Tmdb"}},{"name":"lang","in":"path","required":true,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Subs Vtt","description":"VTT d'une BA (sous-titres MVP 21/07). Public : contenu de BA publique, la page /b/\nanonyme le consommera aussi. Le client tente sa langue et tolère le 404 (fetch lazy,\npas de champ dans le payload feed → zéro toucher au feed-builder).","operationId":"subs_vtt_api_subs__kind___tmdb___lang__vtt_get","parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","title":"Kind"}},{"name":"tmdb","in":"path","required":true,"schema":{"type":"integer","title":"Tmdb"}},{"name":"lang","in":"path","required":true,"schema":{"type":"string","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ba/{code}":{"get":{"summary":"Share Payload","operationId":"share_payload_api_ba__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Share Payload","operationId":"share_payload_api_ba__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ba/{code}/hit":{"post":{"summary":"Share Hit Ep","operationId":"share_hit_ep_api_ba__code__hit_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/seance":{"post":{"summary":"Seance Create","operationId":"seance_create_api_seance_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/seance/{code}":{"get":{"summary":"Seance Payload","operationId":"seance_payload_api_seance__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Seance Payload","operationId":"seance_payload_api_seance__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Seance Update","description":"Édition par le PROPRIÉTAIRE : titre, descriptif (aperçu de partage), item_ids (réordonner /\nretirer / ajouter — revalidés servables, la séance photographie toujours ce qui JOUE).","operationId":"seance_update_api_seance__code__patch","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Seance Delete","operationId":"seance_delete_api_seance__code__delete","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/seance/{code}/hit":{"post":{"summary":"Seance Hit Ep","operationId":"seance_hit_ep_api_seance__code__hit_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/s/{code}":{"get":{"summary":"Seance Page","operationId":"seance_page_s__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Seance Page","operationId":"seance_page_s__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists":{"post":{"summary":"Lists Create","operationId":"lists_create_api_lists_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lists/{code}":{"get":{"summary":"Lists Status","description":"Lecture sans auth : l'agent repasse voir `pending` devenir `playable` (matérialisé) ou\n`no_trailer` (l'acquisition a échoué partout). Réservé aux listes prescripteur (channel).","operationId":"lists_status_api_lists__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Lists Status","description":"Lecture sans auth : l'agent repasse voir `pending` devenir `playable` (matérialisé) ou\n`no_trailer` (l'acquisition a échoué partout). Réservé aux listes prescripteur (channel).","operationId":"lists_status_api_lists__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mcp":{"post":{"summary":"Mcp Endpoint","operationId":"mcp_endpoint_mcp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/for-agents":{"get":{"summary":"For Agents Page","description":"La doc du rayonnage (llms.txt pointe ici ; les agents qui naviguent la lisent).","operationId":"for_agents_page_for_agents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"For Agents Page","description":"La doc du rayonnage (llms.txt pointe ici ; les agents qui naviguent la lisent).","operationId":"for_agents_page_for_agents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/for-curators":{"get":{"summary":"For Curators Page","description":"La page sœur de /for-agents en langage humain (24/08) : la 2e espèce de prescripteurs\n(newsletters, critiques, podcasts). EN primaire, l'audience est US ; démo /s/JWLNPNNu.","operationId":"for_curators_page_for_curators_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"For Curators Page","description":"La page sœur de /for-agents en langage humain (24/08) : la 2e espèce de prescripteurs\n(newsletters, critiques, podcasts). EN primaire, l'audience est US ; démo /s/JWLNPNNu.","operationId":"for_curators_page_for_curators_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pour-les-curateurs":{"get":{"summary":"Pour Les Curateurs Page","operationId":"pour_les_curateurs_page_pour_les_curateurs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Pour Les Curateurs Page","operationId":"pour_les_curateurs_page_pour_les_curateurs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/list":{"get":{"summary":"List Url Create","description":"Création par URL — le canal des agents TEXTE-SEUL (barreau 1, reach max : toute app de\nchat) : l'agent compose `zinema.tv/list?t=Sicario+2015,Wind+River+2017&lang=fr`, la liste\nnaît au premier clic HUMAIN. Idempotente (content_hash : mêmes titres → même code, re-clic\n= redirect instantané). Les bots d'aperçu reçoivent l'OG SANS créer ; robots.txt disallow.","operationId":"list_url_create_list_get","parameters":[{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}},{"name":"title","in":"query","required":false,"schema":{"type":"string","default":"","title":"Title"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/events-anon":{"post":{"summary":"Post Events Anon","description":"Capture anonyme (brique 1, actée 30/07 : « il faut que l'on capte la donnée ») — les\npages publiques émettent avec user_id NULL, session_id = sid client (zn_sid). Principe\nmoteur : l'identité est un attribut de RATTACHEMENT, pas une entrée — même grammaire\nd'events que l'app. Firewall : rien vers le ranking public, tout vers le store de\ntrajectoires. Purge >90 j des sessions jamais rattachées (job à venir, la fenêtre est loin).","operationId":"post_events_anon_api_events_anon_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/adopt-session":{"post":{"summary":"Adopt Session","description":"Rattachement (flux ③) : au signup/login, la session anonyme devient LA trajectoire du\ncompte — elle commençait avant lui (UPDATE par session, même mécanique que les positifs de\nsession des non-Plex). Le tampon de provenance (code de liste) part dans l'event 'adopted'.\n(08/10, lot S, B.19) `filtres` facultatif ({age, avec, type, quoi}, les réglages sans compte de l'appareil) :\nils rejoignent les prefs du compte (_adoption_filtres) et la réponse gagne `migrees`. Sans `filtres`, la\nréponse ne change pas (la télé, les anciens clients). Rejouer l'appel ne change plus rien (idempotent).\n(09/10, refonte des réglages) `filtres` porte aussi `age_memo` et `avec_memo` (D2) ; `plateformes` facultatif\n(D4 : [ids] cochés sur l'appareil, avec `region`, le pays de leur liste) : l'union avec celles du compte\n(_adoption_plateformes), et la réponse gagne `plateformes`, le nombre d'ids ajoutés.\n(11/10, Uniquement mes sources) `filtres` porte aussi `sources` (l'interrupteur de l'appareil) : un compte qui n'a\njamais touché l'interrupteur le prend allumé, et `migrees` gagne {cle: sources, v: true, de: false}.\n(22/10, profils, PROFILS_spec.md 4.5, 4.6 et 5.9) Clés facultatives `principal`, `profils` et `actif` : les profils\ndu navigateur apportés au compte (_adoption_profils), seulement si le drapeau les ouvre à ce compte et que\n`profils` est une liste ; la réponse gagne alors `profils: {local: id}`, et `orphelins` quand il n'est pas nul\n(22/10, sans plafond : plus de `profils_ignores`, tous les profils du navigateur sont gardés). Le rattachement\nd'aujourd'hui ne prend plus que les événements sans `pf` (aucun n'en portait avant les profils : sans les clés\nneuves, ou le drapeau fermé, la réponse et les effets d'avant) ; ceux d'un profil du navigateur passent au compte\navec leur profil, ou restent sans compte. Depuis une session posée sur un profil\nenfant, les clés d'âge de `filtres` sont ignorées (4.6) ; `filtres` reste ceux du principal du navigateur, jamais\nl'âge d'un profil. Le profil de la session est lu avant tout effet (4.3).\n(22/10, relecture du socle) Depuis une session posée sur un profil enfant, `principal`, `profils` et `actif` sont\nignorés en entier, comme drapeau fermé : l'adoption crée des profils, réécrit l'âge d'un profil rapproché par son\npseudo (le plus strict gagne) et le pseudo du principal, trois gestes qu'un enfant n'a pas (4.6 : créer ou modifier\nun profil, 403 ; écrire un âge, « y compris par l'adoption », ignoré ; 8.4). La réponse ne porte pas `profils`, les\névénements marqués restent sans compte, et le navigateur, qui garde les siens, rejouera depuis une session qui en a\nle droit.\n(22/10, deuxième relecture du socle) De même quand les profils que l'appel créerait dépassent ce qui reste de la\nlimite de créations par heure du compte (le seau de POST /api/profils, _adoption_profils) : la réponse ne porte pas\n`profils`, rien n'est créé, rapproché ni effacé, et le navigateur rejoue l'heure suivante.\n(23/10, « La tête d'abord ») `principal` {pseudo, couleur, avatar} (zn_profils.principal) s'adopte aussi QUAND LES\nPROFILS NE S'ADOPTENT PAS (drapeau fermé, ou pas de liste), comme POST /api/profils/modifier s'ouvre au principal\nquel que soit le drapeau : la tête et le pseudo choisis sans compte passent au compte à sa création, chaque clé\nseulement si le compte n'en a pas (_adoption_principal), dans sa propre transaction sous le verrou du compte.\nJamais depuis une session posée sur un profil ajouté quand le drapeau est fermé, ni depuis un enfant\n(_principal_refuse, la règle de la route). La réponse gagne `principal: true` quand le principal du navigateur a\nété lu, écrit ou non (le compte avait déjà le sien) : le navigateur peut alors l'oublier ; absente (refus, échec),\nil le garde et rejouera. Sans la clé `principal`, rien ne change.\n(02/11, lot « Plus tard sans compte ») `plus_tard` facultatif, {\"-\": [ids], \"<local>\": [ids]} (zn_plus_tard) : les\nfilms gardés dans le navigateur rejoignent les listes du compte (_adoption_plus_tard), après les profils et les\nfiltres ; la réponse gagne `plus_tard: {liste: n}`, les listes prises. Jamais depuis une session posée sur un\nprofil enfant. Sans la clé, rien ne change.","operationId":"adopt_session_api_adopt_session_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/film-public/{item_id}":{"get":{"summary":"Film Public","description":"Fiche PUBLIQUE des pages « À l'affiche » (30/07) : la générosité du lien pour l'anonyme —\nmétadonnées + où-regarder par pays, rate-limité par IP, JAMAIS les champs de compte\n(watchlist, sources déclarées, possession Plex).\n(06/10) Aussi la fiche du visiteur SANS COMPTE sur / : /api/film lui répondait 401, il n'avait\nni plateformes ni durée. Il envoie son fuseau (tz) au lieu d'un pays : même doctrine que le feed\nanonyme (_region_anonyme). Sans tz, rien ne change pour les pages publiques (region, défaut FR).\n(21/10, relecture de la carte V2) `carte=1` : la carte V2 du téléphone y lit genres et âge à chaque BA. Son propre\ncompteur (600 par heure et par IP) : sur le compteur commun, une centaine de cartes épuisaient en une heure celui de\nla fiche Infos et d'Où regarder du visiteur sans compte. Ses genres suivent la langue, comme ceux du feed.","operationId":"film_public_api_film_public__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"region","in":"query","required":false,"schema":{"type":"string","default":"FR","title":"Region"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Film Public","description":"Fiche PUBLIQUE des pages « À l'affiche » (30/07) : la générosité du lien pour l'anonyme —\nmétadonnées + où-regarder par pays, rate-limité par IP, JAMAIS les champs de compte\n(watchlist, sources déclarées, possession Plex).\n(06/10) Aussi la fiche du visiteur SANS COMPTE sur / : /api/film lui répondait 401, il n'avait\nni plateformes ni durée. Il envoie son fuseau (tz) au lieu d'un pays : même doctrine que le feed\nanonyme (_region_anonyme). Sans tz, rien ne change pour les pages publiques (region, défaut FR).\n(21/10, relecture de la carte V2) `carte=1` : la carte V2 du téléphone y lit genres et âge à chaque BA. Son propre\ncompteur (600 par heure et par IP) : sur le compteur commun, une centaine de cartes épuisaient en une heure celui de\nla fiche Infos et d'Où regarder du visiteur sans compte. Ses genres suivent la langue, comme ceux du feed.","operationId":"film_public_api_film_public__item_id__get","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"region","in":"query","required":false,"schema":{"type":"string","default":"FR","title":"Region"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/seances":{"get":{"summary":"Seances Mine","description":"« Mes séances » (30/07, spec Thierry) : la liste de SES séances avec compteurs du funnel et\nle lien de partage. Les séances système (sender NULL) n'apparaissent jamais ici.","operationId":"seances_mine_api_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Seances Mine","description":"« Mes séances » (30/07, spec Thierry) : la liste de SES séances avec compteurs du funnel et\nle lien de partage. Les séances système (sender NULL) n'apparaissent jamais ici.","operationId":"seances_mine_api_seances_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/seance-search":{"get":{"summary":"Seance Search","description":"Ajout PAR TITRE au compositeur (spec Thierry) : items servables (BA prête) seulement —\non ne propose jamais un film que la séance ne pourrait pas jouer.\n\n01/09 : cherche dans `appellations`, LE registre des noms, et non plus dans `items.title` qui\nn'est que le titre canonique (l'anglais, dans la plupart des cas). Une curatrice qui tape\n« Le Livre de la jungle » ne trouvait rien, le canon étant « The Jungle Book » ; elle obtient\nmaintenant les quatre adaptations, datées. On renvoie l'appellation RECONNUE (`raw`), pas le\ncanon : c'est le nom qu'elle a cherché, et l'année la départage. `loc` fait préférer\nl'appellation dans SA langue, sinon le canonique — sans quoi « fanfan » rendait\n« Fan-Fan the Tulip » à un francophone, la ligne anglaise portant une confiance plus haute.\n(21/10) Le nom reconnu ne s'affiche plus que dans SA langue ; sinon, son titre dans sa langue (_recherche_titres).","operationId":"seance_search_api_seance_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Seance Search","description":"Ajout PAR TITRE au compositeur (spec Thierry) : items servables (BA prête) seulement —\non ne propose jamais un film que la séance ne pourrait pas jouer.\n\n01/09 : cherche dans `appellations`, LE registre des noms, et non plus dans `items.title` qui\nn'est que le titre canonique (l'anglais, dans la plupart des cas). Une curatrice qui tape\n« Le Livre de la jungle » ne trouvait rien, le canon étant « The Jungle Book » ; elle obtient\nmaintenant les quatre adaptations, datées. On renvoie l'appellation RECONNUE (`raw`), pas le\ncanon : c'est le nom qu'elle a cherché, et l'année la départage. `loc` fait préférer\nl'appellation dans SA langue, sinon le canonique — sans quoi « fanfan » rendait\n« Fan-Fan the Tulip » à un francophone, la ligne anglaise portant une confiance plus haute.\n(21/10) Le nom reconnu ne s'affiche plus que dans SA langue ; sinon, son titre dans sa langue (_recherche_titres).","operationId":"seance_search_api_seance_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recherche":{"get":{"summary":"Recherche","description":"Recherche par titre de la charte Tracé (07/10) : la loupe de l'app, OUVERTE SANS COMPTE.\n\nMême requête que le compositeur (_recherche_titres), réponse {results: [{item_id, title, year, ba, kind}]}.\nTrois différences, toutes voulues :\n  · pas de 401 : le visiteur sans compte cherche aussi (l'app sans compte est la porte d'entrée) ;\n  · quota par visiteur et non par IP seule : une requête par frappe donne vite des dizaines\n    d'appels pour UNE recherche, et une IP mobile partagée (CGNAT) porte beaucoup de monde. La clé\n    est le compte, sinon zn_sid (`sid`, envoyé par l'app) + IP ; 300 par heure. Un filet par IP\n    seule, dix fois plus large, empêche de contourner le quota en changeant de sid à chaque appel ;\n  · (09/10, refonte des réglages, 2.7) l'âge : LA MÊME ÉLIGIBILITÉ QUE LE PIN de /api/feed, dans le mode\n    d'où part la loupe (_recherche_age_ok). En flux : filtres_effectifs puis filtre_age, âge exact, « avec\n    un adulte », fiches, soirée, pour un compte comme pour un appareil, qui envoie son âge dans X-Zn-Flux\n    comme au flux. Avant, un appareil sans compte réglé sur 8 ans voyait « Alien » jouable et le pin le\n    refusait sans un mot ; un compte était jugé au palier natif, sans « avec un adulte » ni fiche. Hors\n    flux (le web sous ?flux=off, l'iPhone en repli) : la règle du pin de ce mode, celle d'avant. Un résultat\n    refusé par l'âge n'est pas rendu (rien n'annonce jouable ce que le pin refusera).\n`mode` (relecture du lot S) : le mode du flux d'où part la loupe, `flux`, `perso` ou `upcoming`, que les\nlots W et I envoient ; absent (les clients d'avant), la règle du flux si X-Zn-Flux est là, celle d'avant\nsinon. `tz` : le MÊME que celui que le client envoie à /api/feed (le pays d'un appareil sans compte pour\nles étiquettes d'âge ; le journal de Caddy et celui d'uvicorn effacent tz). `kind` (09/10) : un film et une\nsérie homonymes se distinguent. `test=1` (le mode ?test=1 des clients) : rien n'est journalisé.\nLa recherche stérile est journalisée pour TOUT LE MONDE, détachée de la personne (Q3, la troisième voie :\n_journalise_recherche_sterile), sauf les comptes du fondateur et de la démo App Review.\n(22/10, profils, 4.5) Chaque réponse rendue porte `profil`, comme le fil ; le 429 n'en porte pas. L'âge est celui\ndu profil de la session (_recherche_age_ok), et `masques: n`, le nombre de résultats retirés par l'âge, part\nseulement quand n > 0, pour toute session où un âge s'applique (avant, le filtre se taisait).\n(28/10, la recherche V2, RECHERCHE_V2_plan.md) `v=2`, que seul le web envoie porte ouverte : _recherche_v2 (les\npersonnes, « Ses films », la vignette, l'âge et la ligne « où » de chaque œuvre). Sans lui, ce chemin, inchangé :\nl'iPhone 1.10 et la télé 1.14 appellent la route à chaque frappe et gardent leurs octets.","operationId":"recherche_api_recherche_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}},{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}},{"name":"test","in":"query","required":false,"schema":{"type":"string","default":"","title":"Test"}},{"name":"v","in":"query","required":false,"schema":{"type":"string","default":"","title":"V"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recherche/personne":{"get":{"summary":"Recherche Personne","description":"(28/10, la recherche V2) Les films d'une personne : {personne: {id, nom, role, n, n_films, n_series, portrait,\nfilms, masques?}, profil}, `personne` à null si elle n'a plus deux œuvres jouables pour ce visiteur. L'identifiant\nTMDB de la personne arrive dans l'en-tête X-Zn-Personne, jamais dans l'URL (relecture du code, 28/10 : l'id est\npublic, dans le journal de Caddy ou d'uvicorn il dirait 14 jours, avec l'IP, qui le visiteur a cherché).","operationId":"recherche_personne_api_recherche_personne_get","parameters":[{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Recherche Personne","description":"(28/10, la recherche V2) Les films d'une personne : {personne: {id, nom, role, n, n_films, n_series, portrait,\nfilms, masques?}, profil}, `personne` à null si elle n'a plus deux œuvres jouables pour ce visiteur. L'identifiant\nTMDB de la personne arrive dans l'en-tête X-Zn-Personne, jamais dans l'URL (relecture du code, 28/10 : l'id est\npublic, dans le journal de Caddy ou d'uvicorn il dirait 14 jours, avec l'IP, qui le visiteur a cherché).","operationId":"recherche_personne_api_recherche_personne_get","parameters":[{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/recherche/accueil":{"get":{"summary":"Recherche Accueil","description":"(28/10, la recherche V2, décision 2 de Thierry) L'ouverture de la recherche : {bloc, items, profil}. `bloc` vaut\n`regardees` quand le classement de la nuit (trois personnes au moins par œuvre) donne cinq œuvres que l'âge du\nvisiteur permet ; sinon `semaine` (relecture du code : même sans complément), complété par la ligne à l'affiche de son\npays (les œuvres avec BA, après l'âge, dans l'ordre de la ligne) ; sans rien, items vide. Jamais un nombre de\npersonnes.","operationId":"recherche_accueil_api_recherche_accueil_get","parameters":[{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Recherche Accueil","description":"(28/10, la recherche V2, décision 2 de Thierry) L'ouverture de la recherche : {bloc, items, profil}. `bloc` vaut\n`regardees` quand le classement de la nuit (trois personnes au moins par œuvre) donne cinq œuvres que l'âge du\nvisiteur permet ; sinon `semaine` (relecture du code : même sans complément), complété par la ligne à l'affiche de son\npays (les œuvres avec BA, après l'âge, dans l'ordre de la ligne) ; sans rien, items vide. Jamais un nombre de\npersonnes.","operationId":"recherche_accueil_api_recherche_accueil_get","parameters":[{"name":"loc","in":"query","required":false,"schema":{"type":"string","default":"","title":"Loc"}},{"name":"sid","in":"query","required":false,"schema":{"type":"string","default":"","title":"Sid"}},{"name":"tz","in":"query","required":false,"schema":{"type":"string","default":"","title":"Tz"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"","title":"Mode"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tautulli/{token}":{"post":{"summary":"Tautulli Webhook","description":"Webhook Tautulli (push trajectoire temps réel — faisabilité 15/07). L'owner configure un\nNotification Agent « Webhook » dans SON Tautulli → POST ici à chaque lecture. Le token identifie\nle compte zinema. On logge le BRUT (webhook_log, inspection) + on enregistre un event\nsource='tautulli' (HORS reco pour l'instant : le modèle lit plex_import → ZÉRO pollution ; on\nmesure couverture/latence). Aligner sur plex_import (dédup vs sync nocturne) = phase 2.","operationId":"tautulli_webhook_api_tautulli__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Tautulli Get Guidance","description":"Un owner COLLE l'URL webhook dans son navigateur pour la « tester » (vécu Thierry 16/07) :\nsans ce GET, elle tombait dans le mount statique → « Not Found » → panique « l'URL est cassée ».\nOn répond en humain : cette URL est pour Tautulli (POST), la page de vérif est à /check.","operationId":"tautulli_get_guidance_api_tautulli__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Tautulli Get Guidance","description":"Un owner COLLE l'URL webhook dans son navigateur pour la « tester » (vécu Thierry 16/07) :\nsans ce GET, elle tombait dans le mount statique → « Not Found » → panique « l'URL est cassée ».\nOn répond en humain : cette URL est pour Tautulli (POST), la page de vérif est à /check.","operationId":"tautulli_get_guidance_api_tautulli__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/tautulli/{token}/check":{"get":{"summary":"Tautulli Check","description":"Auto-vérification OWNER (retour Thierry 16/07 : « ils n'ont aucun moyen de savoir si ça\nfonctionne ») : le owner ouvre CETTE URL dans son navigateur et voit ce que zinema a reçu de\nson Tautulli — le bouton Test y apparaît (placeholders, attendu), une vraie lecture y montre\nson titre. Même classe de secret que l'URL du webhook (le token y est déjà).","operationId":"tautulli_check_api_tautulli__token__check_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Tautulli Check","description":"Auto-vérification OWNER (retour Thierry 16/07 : « ils n'ont aucun moyen de savoir si ça\nfonctionne ») : le owner ouvre CETTE URL dans son navigateur et voit ce que zinema a reçu de\nson Tautulli — le bouton Test y apparaît (placeholders, attendu), une vraie lecture y montre\nson titre. Même classe de secret que l'URL du webhook (le token y est déjà).","operationId":"tautulli_check_api_tautulli__token__check_get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/pro/upload-url":{"post":{"summary":"Pro Upload Url","operationId":"pro_upload_url_api_pro_upload_url_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/pro/submit":{"post":{"summary":"Pro Submit","operationId":"pro_submit_api_pro_submit_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/campaigns":{"get":{"summary":"Admin Campaigns","operationId":"admin_campaigns_api_admin_campaigns_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Admin Campaigns","operationId":"admin_campaigns_api_admin_campaigns_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/campaigns/{cid}/review":{"post":{"summary":"Admin Campaign Review","operationId":"admin_campaign_review_api_admin_campaigns__cid__review_post","parameters":[{"name":"cid","in":"path","required":true,"schema":{"type":"string","title":"Cid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree":{"post":{"summary":"Soiree Create","description":"Le porteur (la TV) ouvre la salle. Une soirée ACTIVE par hôte : re-taper l'icône pendant la\nsoirée rend la même (le lobby se rouvre pour le retardataire, cas nominal). Le paramétrage\nKahoot du lancement (03/08, demande Thierry) : `age_max` — la garde d'âge de LA soirée,\nmodifiable depuis le lobby, appliquée au serve tant que la soirée vit.","operationId":"soiree_create_api_soiree_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/soiree/{code}":{"get":{"summary":"Soiree Info","description":"État public d'une soirée (la page téléphone au chargement).","operationId":"soiree_info_api_soiree__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Soiree Info","description":"État public d'une soirée (la page téléphone au chargement).","operationId":"soiree_info_api_soiree__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/join":{"post":{"summary":"Soiree Join","description":"Un scan = une présence : prénom + avatar, compte OPTIONNEL (si le téléphone est loggé, la\ntrajectoire suivra en brique 2). Le token de présence permet de réagir sans compte.","operationId":"soiree_join_api_soiree__code__join_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/react":{"post":{"summary":"Soiree React","description":"Une réaction : fire-and-forget, EN MÉMOIRE seulement (l'applaudissement n'est pas une\narchive). Cap doux par présence (~6/s) : l'affichage est illimité à l'œil, le serveur ne\nstocke pas le mitraillage au-delà du rendu.","operationId":"soiree_react_api_soiree__code__react_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/regarde":{"post":{"summary":"Soiree Regarde","description":"La TV (l'hôte) déclare le film À L'ÉCRAN — le « maintenant » de la soirée. Sans lui, un\ncœur n'est qu'un applaudissement ; avec lui, c'est un film aimé. (Le téléphone reste bête.)","operationId":"soiree_regarde_api_soiree__code__regarde_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/recolte":{"get":{"summary":"Soiree Recolte","description":"L'écran de fin invité : MES films aimés de la soirée (+ le top de la salle). Lisible\njusqu'à 48 h après l'expiration — le lendemain matin est encore un moment de récolte.","operationId":"soiree_recolte_api_soiree__code__recolte_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Soiree Recolte","description":"L'écran de fin invité : MES films aimés de la soirée (+ le top de la salle). Lisible\njusqu'à 48 h après l'expiration — le lendemain matin est encore un moment de récolte.","operationId":"soiree_recolte_api_soiree__code__recolte_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"token","in":"query","required":false,"schema":{"type":"string","default":"","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/reclame":{"post":{"summary":"Soiree Reclame","description":"L'invité demande à RETROUVER sa récolte : un code de réclamation à usage unique (48 h),\nlié à SA présence. Le lien présence→compte ne se fera qu'à l'encaissement, PAR LUI, sur SON\nappareil, authentifié — conforme au veto d'identité (§3ter).","operationId":"soiree_reclame_api_soiree__code__reclame_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/reclame/encaisse":{"post":{"summary":"Soiree Encaisse","description":"L'encaissement : le porteur du claim, AUTHENTIFIÉ, récupère sa récolte en watchlist et\nrattache SA présence à SON compte (auto-affirmé — la seule porte d'identité autorisée).","operationId":"soiree_encaisse_api_soiree_reclame_encaisse_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/soiree/{code}/end":{"post":{"summary":"Soiree End","description":"L'hôte ferme la salle (trou relevé par Thierry au premier test réel : aucune sortie\nn'existait, la soirée ne mourait qu'à l'expiration). Expire immédiatement ; les téléphones\ndécouvrent la fin à leur prochaine réaction (410 → « la soirée est terminée »).","operationId":"soiree_end_api_soiree__code__end_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/flux":{"get":{"summary":"Soiree Flux","description":"Le flux que la TV polle (~1 Hz) : réactions depuis `since` + présences. Léger et sans état\ncôté client — un reload de la TV reprend au dernier seq.","operationId":"soiree_flux_api_soiree__code__flux_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"since","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Soiree Flux","description":"Le flux que la TV polle (~1 Hz) : réactions depuis `since` + présences. Léger et sans état\ncôté client — un reload de la TV reprend au dernier seq.","operationId":"soiree_flux_api_soiree__code__flux_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}},{"name":"since","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/soiree/{code}":{"get":{"summary":"Soiree Page","description":"La page téléphone — ultra-légère, zéro compte requis (leçon Kahoot n°1).","operationId":"soiree_page_soiree__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Soiree Page","description":"La page téléphone — ultra-légère, zéro compte requis (leçon Kahoot n°1).","operationId":"soiree_page_soiree__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/soiree/{code}/jeu":{"get":{"summary":"Jeu Etat","description":"Bouchon (08/10) : soiree.html, que les télés tvOS installées envoient encore aux invités,\ninterroge cette route chaque seconde. {\"actif\": false} est la réponse « pas de partie » qu'elle\nsait lire (le jeu reste caché, aucune annonce) ; un 404 ferait une ligne de journal par seconde\net par invité. À retirer avec soiree.html, au chantier natif.","operationId":"jeu_etat_api_soiree__code__jeu_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Jeu Etat","description":"Bouchon (08/10) : soiree.html, que les télés tvOS installées envoient encore aux invités,\ninterroge cette route chaque seconde. {\"actif\": false} est la réponse « pas de partie » qu'elle\nsait lire (le jeu reste caché, aucune annonce) ; un 404 ferait une ligne de journal par seconde\net par invité. À retirer avec soiree.html, au chantier natif.","operationId":"jeu_etat_api_soiree__code__jeu_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/login":{"post":{"summary":"Lieu Login","operationId":"lieu_login_api_lieu__slug__login_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/c/{slug}/admin":{"get":{"summary":"Lieu Admin Page","operationId":"lieu_admin_page_c__slug__admin_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"t","in":"query","required":false,"schema":{"type":"string","default":"","title":"T"}},{"name":"expire","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Expire"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/admin/data":{"get":{"summary":"Lieu Admin Data","operationId":"lieu_admin_data_api_lieu__slug__admin_data_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Lieu Admin Data","operationId":"lieu_admin_data_api_lieu__slug__admin_data_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/admin/grille":{"post":{"summary":"Lieu Admin Grille","operationId":"lieu_admin_grille_api_lieu__slug__admin_grille_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/admin/epingle":{"post":{"summary":"Lieu Admin Epingle","description":"La mise en avant (wagon 2, 09/08) : UN film en tête du feed public, assumé par le lieu\n(badge « Le choix du cinéma »). tmdb null = retirer. La seule main de l'exploitant sur\nl'ordre — au-delà, on casserait le tri vivant.","operationId":"lieu_admin_epingle_api_lieu__slug__admin_epingle_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/admin/signale":{"post":{"summary":"Lieu Admin Signale","description":"Signalement d'une BA par l'exploitant (wagon 2) : il connaît sa programmation mieux que\ntout algorithme — chaque signalement = un contrôle d'identité gratuit. Boucle fermée :\nops_log + email IMMÉDIAT vers bonjour@ (une alerte sans action est interdite).","operationId":"lieu_admin_signale_api_lieu__slug__admin_signale_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/lieu/{slug}/admin/meta":{"post":{"summary":"Lieu Admin Meta","operationId":"lieu_admin_meta_api_lieu__slug__admin_meta_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/lieux/{slug}.json":{"get":{"summary":"Lieu Json","description":"La grille d'un lieu, servie depuis la TABLE lieux (le cron scrape_lieux la maintient —\nplus de fichier statique à synchroniser, même URL côté client). meta publics + films\n{tmdb: {v, fiche, s: {date: [{h, v, url_résa_par_séance}]}}} + la date de MAJ.","operationId":"lieu_json_lieux__slug__json_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Lieu Json","description":"La grille d'un lieu, servie depuis la TABLE lieux (le cron scrape_lieux la maintient —\nplus de fichier statique à synchroniser, même URL côté client). meta publics + films\n{tmdb: {v, fiche, s: {date: [{h, v, url_résa_par_séance}]}}} + la date de MAJ.","operationId":"lieu_json_lieux__slug__json_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/salle":{"get":{"summary":"Salle Feed","description":"« En salle » (incrément 2, MODELE_sources_v2) : le feed FUSIONNÉ des lieux gardés\n(zn_cine, passés en ?cines=slug1,slug2,slug3). Union des programmations ; un même film à\nl'affiche dans deux de mes cinémas = UNE carte dont chaque séance porte son lieu (le point\n« doublons En salle » du doc). Réutilise _seance_items (BA-ready). Langue et fuseau\nsuivent les LIEUX (25/08) : un feed de salles US/ZA parle anglais et trie ses séances\ndans le fuseau de chaque salle.","operationId":"salle_feed_api_salle_get","parameters":[{"name":"cines","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cines"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Salle Feed","description":"« En salle » (incrément 2, MODELE_sources_v2) : le feed FUSIONNÉ des lieux gardés\n(zn_cine, passés en ?cines=slug1,slug2,slug3). Union des programmations ; un même film à\nl'affiche dans deux de mes cinémas = UNE carte dont chaque séance porte son lieu (le point\n« doublons En salle » du doc). Réutilise _seance_items (BA-ready). Langue et fuseau\nsuivent les LIEUX (25/08) : un feed de salles US/ZA parle anglais et trie ses séances\ndans le fuseau de chaque salle.","operationId":"salle_feed_api_salle_get","parameters":[{"name":"cines","in":"query","required":false,"schema":{"type":"string","default":"","title":"Cines"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/mes-salles":{"get":{"summary":"Page Mes Salles","description":"« En salle » : coquille cliente du feed fusionné. index.html lit zn_cine (localStorage)\net appelle /api/salle. Déclarée avant l'attrape-tout /{cc}/{slug}.","operationId":"page_mes_salles_mes_salles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Mes Salles","description":"« En salle » : coquille cliente du feed fusionné. index.html lit zn_cine (localStorage)\net appelle /api/salle. Déclarée avant l'attrape-tout /{cc}/{slug}.","operationId":"page_mes_salles_mes_salles_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/cinemas":{"get":{"summary":"Page Cinemas","description":"« Votre cinéma de quartier » : géoloc → salles proches → page servie ou demande.\n\nLe HUB SEO (12/08) : l'index des VILLES couvertes, qui porte le maillage interne vers les\npages ville /cinemas/{ville}. Déclarée avant l'attrape-tout /{cc}/{slug} (l'ordre tranche).\n\n⚠️ FIN DU SERVICE DIFFÉRENCIÉ (15/08). La v1 réservait cet index à Googlebot et rendait à\nl'humain la seule géoloc : 49 Ko et 195 liens d'un côté, 13 Ko et zéro lien de l'autre. C'est\ndu CLOAKING au sens des règles Google. Le motif était innocent — l'humain a un GPS, le bot\nn'en a pas — mais leurs systèmes ne jugent pas l'intention, et ils recrawlent justement avec\ndes UA de navigateur pour repérer l'écart. Sur une propriété jeune dont 15 pages salle sur 16\nsont « détectées, actuellement non indexées », c'était un risque qu'on ne pouvait pas se\npermettre de garder.\nDésormais UN SEUL document pour tout le monde : la page humaine porte la géoloc ET l'index,\ninjecté ici (placeholder <!--VILLES-->). Bénéfice au passage : les gens qui REFUSENT la\ngéolocalisation — nombreux — ont enfin quelque chose à parcourir.\nLe rendu bot complet reste servi aux aperçus sociaux, qui n'exécutent pas de JS et n'ont que\nfaire d'un bouton : eux ne comparent pas deux versions, ils fabriquent une vignette.","operationId":"page_cinemas_cinemas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Cinemas","description":"« Votre cinéma de quartier » : géoloc → salles proches → page servie ou demande.\n\nLe HUB SEO (12/08) : l'index des VILLES couvertes, qui porte le maillage interne vers les\npages ville /cinemas/{ville}. Déclarée avant l'attrape-tout /{cc}/{slug} (l'ordre tranche).\n\n⚠️ FIN DU SERVICE DIFFÉRENCIÉ (15/08). La v1 réservait cet index à Googlebot et rendait à\nl'humain la seule géoloc : 49 Ko et 195 liens d'un côté, 13 Ko et zéro lien de l'autre. C'est\ndu CLOAKING au sens des règles Google. Le motif était innocent — l'humain a un GPS, le bot\nn'en a pas — mais leurs systèmes ne jugent pas l'intention, et ils recrawlent justement avec\ndes UA de navigateur pour repérer l'écart. Sur une propriété jeune dont 15 pages salle sur 16\nsont « détectées, actuellement non indexées », c'était un risque qu'on ne pouvait pas se\npermettre de garder.\nDésormais UN SEUL document pour tout le monde : la page humaine porte la géoloc ET l'index,\ninjecté ici (placeholder <!--VILLES-->). Bénéfice au passage : les gens qui REFUSENT la\ngéolocalisation — nombreux — ont enfin quelque chose à parcourir.\nLe rendu bot complet reste servi aux aperçus sociaux, qui n'exécutent pas de JS et n'ont que\nfaire d'un bouton : eux ne comparent pas deux versions, ils fabriquent une vignette.","operationId":"page_cinemas_cinemas_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/salles/proches":{"get":{"summary":"Salles Proches","description":"Géoloc → les cinémas autour (chantier « mon cinéma de quartier », GO 12/08).\nsalles_ref = nos lieux servis (slug) + SCARE (création auto possible) + OSM (prospect).\nLe client n'a que deux états : servie (→ /c/slug) ou demandable (→ /api/salles/demande).\n« Le bassin, jamais le point » (décision Thierry 07/10) : l'event ne garde AUCUNE position,\nseulement le nombre de salles trouvées. La politique de confidentialité le promet.","operationId":"salles_proches_api_salles_proches_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","title":"Lat"}},{"name":"lon","in":"query","required":true,"schema":{"type":"number","title":"Lon"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/salles/search":{"get":{"summary":"Salles Search","description":"Recherche de cinémas par VILLE ou NOM (rayon Lieux de l'app store des sources, incrément 3) :\non tape « Lille » ou « Méliès » depuis les Réglages pour déclarer sa salle comme on coche une\nplateforme, sans géoloc. Même format que /api/salles/proches (servie → /c/slug, sinon demandable).\nServies d'abord (elles ont déjà une page), puis par nom.","operationId":"salles_search_api_salles_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Salles Search","description":"Recherche de cinémas par VILLE ou NOM (rayon Lieux de l'app store des sources, incrément 3) :\non tape « Lille » ou « Méliès » depuis les Réglages pour déclarer sa salle comme on coche une\nplateforme, sans géoloc. Même format que /api/salles/proches (servie → /c/slug, sinon demandable).\nServies d'abord (elles ont déjà une page), puis par nom.","operationId":"salles_search_api_salles_search_get","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string","default":"","title":"Q"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evenements/proches":{"get":{"summary":"Evenements Proches","description":"Géoloc → l'événementiel (ballet/opéra/théâtre/ciné-conférence) projeté PRÈS DE TOI. Un\névénement par ligne (la salle la plus proche + sa prochaine séance), gate BA (mux_playback_id).\nsalle_evenement_seance.salle_ref = salles_ref.slug (les Kinepolis sont servis, avec GPS).\nAlimente la section « près de toi » de /cinemas. Un ballet = une sortie : rayon large assumé.","operationId":"evenements_proches_api_evenements_proches_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","title":"Lat"}},{"name":"lon","in":"query","required":true,"schema":{"type":"number","title":"Lon"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Evenements Proches","description":"Géoloc → l'événementiel (ballet/opéra/théâtre/ciné-conférence) projeté PRÈS DE TOI. Un\névénement par ligne (la salle la plus proche + sa prochaine séance), gate BA (mux_playback_id).\nsalle_evenement_seance.salle_ref = salles_ref.slug (les Kinepolis sont servis, avec GPS).\nAlimente la section « près de toi » de /cinemas. Un ballet = une sortie : rayon large assumé.","operationId":"evenements_proches_api_evenements_proches_get","parameters":[{"name":"lat","in":"query","required":true,"schema":{"type":"number","title":"Lat"}},{"name":"lon","in":"query","required":true,"schema":{"type":"number","title":"Lon"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/salles/demande":{"post":{"summary":"Salles Demande","description":"« Je veux mon cinéma sur zinema » — le cœur du pivot claim freemium : la page se\ncrée pour l'USER (jamais éteinte), l'exploitant revendiquera. Deux chemins :\n- salle SCARE (id du référentiel) → création AUTOMATIQUE (ajoute_lieu --scare fait\n  lieu + grille + seance), plafonnée 10/j — au-delà on note en prospect ;\n- salle OSM ou saisie libre → ops_log prospect : CHAQUE demande est une ligne, le\n  count par salle = la preuve de demande qu'on montre à l'exploitant.","operationId":"salles_demande_api_salles_demande_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/c/{slug}":{"get":{"summary":"Vitrine Lieu","description":"Vitrine LIEU (prototype 09/08, offre cinémas) : /c/melies = le feed d'UNE salle — la\ngrammaire du feed en mode public, scopée par la ligne seances `cinema-{slug}` (principe\ntranché 08/08 : les canaux se mélangent, les LIEUX se visitent). L'habillage lieu\n(en-tête, prochaine séance par carte, fin avec adresse) vit côté client sur le JSON\nstatique /static/lieux/{slug}.json — zéro migration pour le prototype ; la table lieux\net la MAJ ISA attendent le verdict Vitrine. noindex partout : page d'essai, pas un actif\nSEO (le SEO gratuit = les pages VILLE, décision 08/08). Déclarée AVANT l'attrape-tout\n/{cc}/{slug} (« c » n'est pas un pays de la carte, l'ordre de déclaration tranche).","operationId":"vitrine_lieu_c__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Vitrine Lieu","description":"Vitrine LIEU (prototype 09/08, offre cinémas) : /c/melies = le feed d'UNE salle — la\ngrammaire du feed en mode public, scopée par la ligne seances `cinema-{slug}` (principe\ntranché 08/08 : les canaux se mélangent, les LIEUX se visitent). L'habillage lieu\n(en-tête, prochaine séance par carte, fin avec adresse) vit côté client sur le JSON\nstatique /static/lieux/{slug}.json — zéro migration pour le prototype ; la table lieux\net la MAJ ISA attendent le verdict Vitrine. noindex partout : page d'essai, pas un actif\nSEO (le SEO gratuit = les pages VILLE, décision 08/08). Déclarée AVANT l'attrape-tout\n/{cc}/{slug} (« c » n'est pas un pays de la carte, l'ordre de déclaration tranche).","operationId":"vitrine_lieu_c__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/vignette/{pb}/{t}/{w}.jpg":{"get":{"summary":"Vignette","operationId":"vignette_vignette__pb___t___w__jpg_get","parameters":[{"name":"pb","in":"path","required":true,"schema":{"type":"string","title":"Pb"}},{"name":"t","in":"path","required":true,"schema":{"type":"integer","title":"T"}},{"name":"w","in":"path","required":true,"schema":{"type":"integer","title":"W"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Vignette","operationId":"vignette_vignette__pb___t___w__jpg_get","parameters":[{"name":"pb","in":"path","required":true,"schema":{"type":"string","title":"Pb"}},{"name":"t","in":"path","required":true,"schema":{"type":"integer","title":"T"}},{"name":"w","in":"path","required":true,"schema":{"type":"integer","title":"W"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/e/{slug}":{"get":{"summary":"Page Evenement","description":"Page ÉVÉNEMENT (12/08, affichage de l'événementiel de salle) : un ballet/opéra/théâtre/\nciné-conférence = sa BANDE-ANNONCE qui joue + sa TOURNÉE (où le voir, par salle). Gate BA :\nsans mux_playback_id, pas de page (404). Indexable, JSON-LD DanceEvent/TheaterEvent/…","operationId":"page_evenement_e__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Evenement","description":"Page ÉVÉNEMENT (12/08, affichage de l'événementiel de salle) : un ballet/opéra/théâtre/\nciné-conférence = sa BANDE-ANNONCE qui joue + sa TOURNÉE (où le voir, par salle). Gate BA :\nsans mux_playback_id, pas de page (404). Indexable, JSON-LD DanceEvent/TheaterEvent/…","operationId":"page_evenement_e__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cinemas/{ville}":{"get":{"summary":"Page Ville","description":"Chemin B du SEO salles : l'annuaire d'une VILLE — ses salles servies + les films de la\nsemaine, la requête reine « cinéma {ville} ». Page HTML server-side (pas le feed app),\nindexable, servie à TOUS (humains + moteurs + agents). Déclarée avant l'attrape-tout.","operationId":"page_ville_cinemas__ville__get","parameters":[{"name":"ville","in":"path","required":true,"schema":{"type":"string","title":"Ville"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Ville","description":"Chemin B du SEO salles : l'annuaire d'une VILLE — ses salles servies + les films de la\nsemaine, la requête reine « cinéma {ville} ». Page HTML server-side (pas le feed app),\nindexable, servie à TOUS (humains + moteurs + agents). Déclarée avant l'attrape-tout.","operationId":"page_ville_cinemas__ville__get","parameters":[{"name":"ville","in":"path","required":true,"schema":{"type":"string","title":"Ville"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/film/{slug}":{"get":{"summary":"Page Film","description":"/film/{titre}-{tmdb} : la bande-annonce qui joue + les salles qui le programment.","operationId":"page_film_film__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Film","description":"/film/{titre}-{tmdb} : la bande-annonce qui joue + les salles qui le programment.","operationId":"page_film_film__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/age":{"get":{"summary":"Page Age Racine","description":"/age : les dernières fiches « pour quel âge », la plus récente en premier (05/09).","operationId":"page_age_racine_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Age Racine","description":"/age : les dernières fiches « pour quel âge », la plus récente en premier (05/09).","operationId":"page_age_racine_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/en/age":{"get":{"summary":"Page Age Racine En","description":"/en/age : la même page en anglais (05/09, go Thierry : le SEO autant en anglais qu'en français).","operationId":"page_age_racine_en_en_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Age Racine En","description":"/en/age : la même page en anglais (05/09, go Thierry : le SEO autant en anglais qu'en français).","operationId":"page_age_racine_en_en_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/es/age":{"get":{"summary":"Page Age Racine Es","description":"/es/age : la même page en espagnol (17/09, go Thierry : les 25 fiches les plus demandées, l'Espagne et\nl'Amérique hispanophone cherchent « para qué edad » comme la France cherche « pour quel âge »).","operationId":"page_age_racine_es_es_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Age Racine Es","description":"/es/age : la même page en espagnol (17/09, go Thierry : les 25 fiches les plus demandées, l'Espagne et\nl'Amérique hispanophone cherchent « para qué edad » comme la France cherche « pour quel âge »).","operationId":"page_age_racine_es_es_age_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/age/titres":{"get":{"summary":"Api Age Titres","description":"(18/09) les titres des fiches en ligne dans une langue, pour l'autocomplétion du champ de recherche des fiches :\nchargés au premier focus du champ, filtrés côté client comme sur le hub. Public, court, cache 5 min.","operationId":"api_age_titres_api_age_titres_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Api Age Titres","description":"(18/09) les titres des fiches en ligne dans une langue, pour l'autocomplétion du champ de recherche des fiches :\nchargés au premier focus du champ, filtrés côté client comme sur le hub. Public, court, cache 5 min.","operationId":"api_age_titres_api_age_titres_get","parameters":[{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/age/a-{age}-ans":{"get":{"summary":"Page A Voir Fr","operationId":"page_a_voir_fr_age_a__age__ans_get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page A Voir Fr","operationId":"page_a_voir_fr_age_a__age__ans_get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/en/age/at-{age}":{"get":{"summary":"Page A Voir En","operationId":"page_a_voir_en_en_age_at__age__get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page A Voir En","operationId":"page_a_voir_en_en_age_at__age__get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/es/age/a-los-{age}":{"get":{"summary":"Page A Voir Es","operationId":"page_a_voir_es_es_age_a_los__age__get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page A Voir Es","operationId":"page_a_voir_es_es_age_a_los__age__get","parameters":[{"name":"age","in":"path","required":true,"schema":{"type":"integer","title":"Age"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/age/methode":{"get":{"summary":"Page Age Methode","operationId":"page_age_methode_age_methode_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Age Methode","operationId":"page_age_methode_age_methode_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/en/age/method":{"get":{"summary":"Page Age Methode En","operationId":"page_age_methode_en_en_age_method_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Page Age Methode En","operationId":"page_age_methode_en_en_age_method_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/en/age/{slug}":{"get":{"summary":"Page Age En","operationId":"page_age_en_en_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Age En","operationId":"page_age_en_en_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/es/age/{slug}":{"get":{"summary":"Page Age Es","operationId":"page_age_es_es_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Age Es","operationId":"page_age_es_es_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/age/{slug}":{"get":{"summary":"Page Age","description":"/age/{slug} : la fiche « pour quel âge » d'une œuvre (04/09). Le contenu vit dans fiches_age (une ligne par\nœuvre, corps HTML produit par un générateur, note jamais sous la classification du pays). Brouillon = 404 pour\nle public, visible par un admin en noindex. Première fiche : Stranger Things, la demande SEO la plus mesurée.","operationId":"page_age_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Age","description":"/age/{slug} : la fiche « pour quel âge » d'une œuvre (04/09). Le contenu vit dans fiches_age (une ligne par\nœuvre, corps HTML produit par un générateur, note jamais sous la classification du pays). Brouillon = 404 pour\nle public, visible par un admin en noindex. Première fiche : Stranger Things, la demande SEO la plus mesurée.","operationId":"page_age_age__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}},{"name":"lang","in":"query","required":false,"schema":{"type":"string","default":"fr","title":"Lang"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sitemap.xml":{"get":{"summary":"Sitemap","description":"L'index : une entrée par famille, datée de sa page la plus récente.","operationId":"sitemap_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Sitemap","description":"L'index : une entrée par famille, datée de sa page la plus récente.","operationId":"sitemap_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sitemap-{famille}.xml":{"get":{"summary":"Sitemap Famille","operationId":"sitemap_famille_sitemap__famille__xml_get","parameters":[{"name":"famille","in":"path","required":true,"schema":{"type":"string","title":"Famille"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Sitemap Famille","operationId":"sitemap_famille_sitemap__famille__xml_get","parameters":[{"name":"famille","in":"path","required":true,"schema":{"type":"string","title":"Famille"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/32b8c04d1e7340adb2ef7f765314827e.txt":{"get":{"summary":"Indexnow Key","description":"Le fichier que Bing va lire pour vérifier que la soumission vient bien du propriétaire.\nIl ne contient que la clé, sans retour à la ligne parasite : les validateurs sont stricts.","operationId":"indexnow_key_32b8c04d1e7340adb2ef7f765314827e_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Indexnow Key","description":"Le fichier que Bing va lire pour vérifier que la soumission vient bien du propriétaire.\nIl ne contient que la clé, sans retour à la ligne parasite : les validateurs sont stricts.","operationId":"indexnow_key_32b8c04d1e7340adb2ef7f765314827e_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/llms.txt":{"get":{"summary":"Llms Txt","description":"Le « signe » pour les agents (12/08) : décrit zinema aux LLM et pointe les données\npropres (programmation salles, sorties par pays), au format llms.txt émergent.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Llms Txt","description":"Le « signe » pour les agents (12/08) : décrit zinema aux LLM et pointe les données\npropres (programmation salles, sorties par pays), au format llms.txt émergent.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/{cc}/new-on/{service}":{"get":{"summary":"Nouveau Public","description":"/fr/nouveautes/netflix, /za/new-on/prime-video. Mauvais slug pour la langue du pays → 308 vers le\ncanonique ; pays ou service sans ligne → 404. Robots : la page liée ; humains : index.html en mode public\n(le client dérive le code nouveau-{cc}-{service} du chemin et lit /api/seance/{code}).","operationId":"nouveau_public__cc__new_on__service__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Nouveau Public","description":"/fr/nouveautes/netflix, /za/new-on/prime-video. Mauvais slug pour la langue du pays → 308 vers le\ncanonique ; pays ou service sans ligne → 404. Robots : la page liée ; humains : index.html en mode public\n(le client dérive le code nouveau-{cc}-{service} du chemin et lit /api/seance/{code}).","operationId":"nouveau_public__cc__new_on__service__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{cc}/nouveautes/{service}":{"get":{"summary":"Nouveau Public","description":"/fr/nouveautes/netflix, /za/new-on/prime-video. Mauvais slug pour la langue du pays → 308 vers le\ncanonique ; pays ou service sans ligne → 404. Robots : la page liée ; humains : index.html en mode public\n(le client dérive le code nouveau-{cc}-{service} du chemin et lit /api/seance/{code}).","operationId":"nouveau_public__cc__nouveautes__service__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Nouveau Public","description":"/fr/nouveautes/netflix, /za/new-on/prime-video. Mauvais slug pour la langue du pays → 308 vers le\ncanonique ; pays ou service sans ligne → 404. Robots : la page liée ; humains : index.html en mode public\n(le client dérive le code nouveau-{cc}-{service} du chemin et lit /api/seance/{code}).","operationId":"nouveau_public__cc__nouveautes__service__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"service","in":"path","required":true,"schema":{"type":"string","title":"Service"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{cc}/sorties/{ym}":{"get":{"summary":"Affiche Mois","description":"(14/09, pari 1 de RECHERCHE_requetes_fr_europe.md, go Thierry) LE CALENDRIER DES SORTIES AU MOIS.\n« sortie cinéma septembre 2026 », « … octobre », « … décembre 2025 » : dix complétions sur treize sont un\nmois, et la requête survit à son mois. Une URL par mois qui ne se périme pas : /fr/sorties/2026-10. Le\ncontenu vit dans la ligne seances affiche-fr-{ym} (affiche_page.py, mois précédent, courant, suivant), la\npage est la même que « à l'affiche » avec une fenêtre plus large (mode mois de _affiche_seo_html).\nLe piège du dossier : dupliquer la semaine dans le mois courant → le mois EN COURS a pour canonical la\npage de la semaine ; le mois révolu et le mois suivant sont canoniques d'eux-mêmes. France seulement\npour l'instant (le gel du 07/09 : la France).","operationId":"affiche_mois__cc__sorties__ym__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"ym","in":"path","required":true,"schema":{"type":"string","title":"Ym"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Affiche Mois","description":"(14/09, pari 1 de RECHERCHE_requetes_fr_europe.md, go Thierry) LE CALENDRIER DES SORTIES AU MOIS.\n« sortie cinéma septembre 2026 », « … octobre », « … décembre 2025 » : dix complétions sur treize sont un\nmois, et la requête survit à son mois. Une URL par mois qui ne se périme pas : /fr/sorties/2026-10. Le\ncontenu vit dans la ligne seances affiche-fr-{ym} (affiche_page.py, mois précédent, courant, suivant), la\npage est la même que « à l'affiche » avec une fenêtre plus large (mode mois de _affiche_seo_html).\nLe piège du dossier : dupliquer la semaine dans le mois courant → le mois EN COURS a pour canonical la\npage de la semaine ; le mois révolu et le mois suivant sont canoniques d'eux-mêmes. France seulement\npour l'instant (le gel du 07/09 : la France).","operationId":"affiche_mois__cc__sorties__ym__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"ym","in":"path","required":true,"schema":{"type":"string","title":"Ym"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/{cc}/{slug}":{"get":{"summary":"Affiche Public","description":"Page publique « À l'affiche » par PAYS (30/07) : le feed de l'app en mode anonyme — la\ngrammaire unique (décision Thierry : « trop différent » = la page séance clonée est morte).\nLe contenu vit dans la ligne seances `affiche-{cc}` (picker affiche_page.py, jour de sortie\nnational). index.html détecte le chemin et bascule en mode public.","operationId":"affiche_public__cc___slug__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Affiche Public","description":"Page publique « À l'affiche » par PAYS (30/07) : le feed de l'app en mode anonyme — la\ngrammaire unique (décision Thierry : « trop différent » = la page séance clonée est morte).\nLe contenu vit dans la ligne seances `affiche-{cc}` (picker affiche_page.py, jour de sortie\nnational). index.html détecte le chemin et bascule en mode public.","operationId":"affiche_public__cc___slug__get","parameters":[{"name":"cc","in":"path","required":true,"schema":{"type":"string","title":"Cc"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}