{"openapi":"3.1.0","info":{"title":"PharmAssistant API","description":"\nAccès programmatique à l'assistant pharmaceutique **PharmAssistant** : chaque question suit le même moteur deux étapes que le chat (routage vers les bases documentaires spécialisées, exécution des outils, rédaction sourcée) et revient en **JSON structuré**, non streamé, sans aucun marqueur d'IA interne. Clés du contrat en anglais, contenus en français.\n\n### Authentification\n\nToutes les routes, sauf `/v1/health`, exigent une clé d'API au format `pa_…`, transmise au choix :\n\n| En-tête | Exemple |\n|---|---|\n| `X-API-Key` | `X-API-Key: pa_votre_cle` |\n| `Authorization` | `Authorization: Bearer pa_votre_cle` |\n\nLes clés sont délivrées par l'équipe PharmAssistant ([contact@pharmassistant.fr](mailto:contact@pharmassistant.fr)) : vous recevez un lien vers un portail sécurisé où vous générez votre clé vous-même et gérez sa péremption et ses plafonds.\n\n### Démarrage rapide\n\n```bash\ncurl \"https://api.pharmassistant.fr/v1/ask\" \\\n  -H \"X-API-Key: pa_votre_cle\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\": \"Amoxicilline et allaitement ?\"}'\n```\n\nPour un test immédiat sans corps JSON : `GET /v1/ask?q=…`. Des clients officiels **Python** (`/clients/pharmassistant.py`), **TypeScript** (`/clients/pharmassistant.ts`) et **Java** (`/clients/PharmAssistant.java`), sans aucune dépendance, sont servis par l'API elle-même.\n\n### Conversations\n\nPar défaut l'API est sans état (le client peut porter l'historique via `history`). Avec `store: true`, le serveur ouvre une **conversation** et renvoie son `conversation_id` (`conv_…`) : les tours suivants n'envoient plus que `question` + `conversation_id`, l'historique est rechargé côté serveur. `GET /v1/conversations` liste, `GET`/`DELETE /v1/conversations/{id}` relit ou supprime ; purge automatique après 90 jours d'inactivité.\n\n### Streaming\n\n`\"stream\": true` (POST seulement) fait passer la réponse en **Server-Sent Events** (`text/event-stream`) : événements `start`, `status` (state : routing / tools_running / generating), `chunk` (fragments de la réponse), puis `done` (le même objet complet que la réponse non streamée) ou `error` (enveloppe habituelle, requête remboursée). Les erreurs de pré-vol restent des statuts HTTP classiques.\n\n### Modes de réponse\n\n- `standard` : la réponse rapide du comptoir, sourcée et structurée (titre, réponse markdown, points clés à polarité, discours patient, vente associée, pistes d'approfondissement, sources complètes par base).\n- `detailed` : le moteur « Réponse détaillée » (analyse exhaustive, entité par entité), soumis à la portée `ask:detailed` de la clé et à son éventuel sous-plafond journalier.\n\n### Quotas et suivi\n\nLa facturation compte des **requêtes**, pas des tokens. Chaque réponse porte les en-têtes `X-RateLimit-Limit`, `X-RateLimit-Remaining` et `X-RateLimit-Reset` (réinitialisation à minuit, heure de Paris), plus un `X-Request-Id` à joindre à toute demande de support. `GET /v1/me` renvoie l'état complet de la clé : plafonds, consommation du jour et du mois.\n\n### Erreurs\n\nToutes les erreurs partagent la même enveloppe `{\"error\": {\"code\", \"message\", \"details\"}}` avec un code machine stable (`daily_limit_reached`, `rate_limited`, `expired_api_key`…). Une requête refusée ou échouée (429, 500, 502) n'est **jamais décomptée**.\n","version":"1.0.0"},"paths":{"/v1/health":{"get":{"tags":["Service"],"summary":"État du service","operationId":"health_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/v1/me":{"get":{"tags":["Clé d'API"],"summary":"Ma clé et mes quotas","operationId":"me_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyInfoResponse"}}}},"401":{"description":"Clé d'API absente, invalide ou révoquée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"CleApi":[]}]}},"/v1/ask":{"post":{"tags":["Questions"],"summary":"Poser une question (JSON)","description":"Forme nominale : JSON, seul `question` est obligatoire. Avec\n`stream: true`, la réponse passe en Server-Sent Events (text/event-stream) :\névénements `start`, `status` ({state: routing | tools_running | generating}),\n`chunk` ({text}), puis `done` (l'objet AskResponse complet, identique au mode\nnon streamé) ou `error` (enveloppe d'erreur habituelle, quota remboursé).\nLes erreurs de pré-vol (auth, portée, quotas, conversation inconnue) restent\ndes statuts HTTP classiques.","operationId":"ask_v1_ask_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"401":{"description":"Clé d'API absente, invalide, révoquée ou expirée (expired_api_key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Pharmacie liée à la clé introuvable ou suspendue (pharmacy_not_found / pharmacy_suspended), ou portée manquante sur la clé (scope_denied).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"conversation_id inconnu pour cette clé (conversation_not_found : id invalide, conversation supprimée ou purgée après 90 jours d'inactivité). Requête non décomptée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Corps ou paramètres invalides (détail champ par champ dans error.details).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Trop de requêtes : plafond journalier ou mensuel atteint (codes daily_limit_reached / monthly_limit_reached / detailed_limit_reached, reset à minuit ou au 1er du mois heure de Paris) ou rafale (code rate_limited, réessayer après l'en-tête Retry-After). Requête non décomptée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Erreur interne. Requête non décomptée (quota remboursé).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Échec de génération ou réponse vide du moteur. Requête non décomptée (quota remboursé), réessayer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"CleApi":[]}]},"get":{"tags":["Questions"],"summary":"Poser une question (test rapide, paramètre q)","description":"Forme minimale : GET /v1/ask?q=… (test rapide au curl/navigateur, sans corps\nJSON). Les paramètres optionnels reprennent ceux du POST : store=true ouvre une\nconversation côté serveur, conversation_id=conv_… la poursuit (l'historique\nclient, lui, n'existe qu'en POST).","operationId":"ask_get_v1_ask_get","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","title":"Q"}},{"name":"mode","in":"query","required":false,"schema":{"type":"string","default":"standard","title":"Mode"}},{"name":"suggestions","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Suggestions"}},{"name":"patient_advice","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Patient Advice"}},{"name":"associated_sale","in":"query","required":false,"schema":{"type":"boolean","default":true,"title":"Associated Sale"}},{"name":"instructions","in":"query","required":false,"schema":{"type":"string","title":"Instructions"}},{"name":"store","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Store"}},{"name":"conversation_id","in":"query","required":false,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"401":{"description":"Clé d'API absente, invalide, révoquée ou expirée (expired_api_key).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Pharmacie liée à la clé introuvable ou suspendue (pharmacy_not_found / pharmacy_suspended), ou portée manquante sur la clé (scope_denied).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"conversation_id inconnu pour cette clé (conversation_not_found : id invalide, conversation supprimée ou purgée après 90 jours d'inactivité). Requête non décomptée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Corps ou paramètres invalides (détail champ par champ dans error.details).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Trop de requêtes : plafond journalier ou mensuel atteint (codes daily_limit_reached / monthly_limit_reached / detailed_limit_reached, reset à minuit ou au 1er du mois heure de Paris) ou rafale (code rate_limited, réessayer après l'en-tête Retry-After). Requête non décomptée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Erreur interne. Requête non décomptée (quota remboursé).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Échec de génération ou réponse vide du moteur. Requête non décomptée (quota remboursé), réessayer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"CleApi":[]}]}},"/v1/conversations":{"get":{"tags":["Conversations"],"summary":"Lister mes conversations","description":"Conversations gérées côté serveur de la clé (créées par un ask avec\nstore=true), la plus récemment active d'abord. `limit` : 1 à 200 (défaut 50).","operationId":"list_conversations_v1_conversations_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationListResponse"}}}},"401":{"description":"Clé d'API absente, invalide ou révoquée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"CleApi":[]}]}},"/v1/conversations/{conversation_id}":{"get":{"tags":["Conversations"],"summary":"Relire une conversation","description":"Détail d'une conversation : titre, dates et tous les tours (user/assistant)\ndans l'ordre chronologique.","operationId":"get_conversation_v1_conversations__conversation_id__get","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDetailResponse"}}}},"401":{"description":"Clé d'API absente, invalide, révoquée ou expirée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Conversation inconnue pour cette clé (conversation_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"CleApi":[]}]},"delete":{"tags":["Conversations"],"summary":"Supprimer une conversation","description":"Suppression immédiate et définitive (messages inclus). Sans appel explicite,\nune conversation est purgée automatiquement après 90 jours d'inactivité.","operationId":"delete_conversation_v1_conversations__conversation_id__delete","parameters":[{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","title":"Conversation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationDeletedResponse"}}}},"401":{"description":"Clé d'API absente, invalide, révoquée ou expirée.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Conversation inconnue pour cette clé (conversation_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"CleApi":[]}]}}},"components":{"schemas":{"AskRequest":{"properties":{"question":{"type":"string","maxLength":4000,"minLength":1,"title":"Question","description":"Question du pharmacien, en français."},"history":{"items":{"$ref":"#/components/schemas/HistoryTurn"},"type":"array","maxItems":24,"title":"History","description":"Tours précédents de la conversation quand le CLIENT porte l'état (mode sans stockage). Exclusif de conversation_id et de store."},"conversation_id":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"Conversation Id","description":"Continuer une conversation gérée côté serveur (id conv_… renvoyé par un ask avec store=true) : l'historique est rechargé par le serveur et le nouvel échange y est ajouté. Exclusif de history."},"store":{"type":"boolean","title":"Store","description":"true = créer une conversation côté serveur : l'échange est conservé (90 jours d'inactivité max) et la réponse porte son conversation_id, à renvoyer aux tours suivants. Inutile avec conversation_id (déjà stocké). Exclusif de history.","default":false},"mode":{"type":"string","enum":["standard","detailed"],"title":"Mode","description":"detailed = moteur Réponse détaillée (à froid).","default":"standard"},"suggestions":{"type":"boolean","title":"Suggestions","description":"Autoriser les pistes d'approfondissement (mode standard seulement).","default":true},"patient_advice":{"type":"boolean","title":"Patient Advice","description":"Inclure le discours patient (bloc « Au comptoir »). False = le bloc est retiré et le champ reste null.","default":true},"associated_sale":{"type":"boolean","title":"Associated Sale","description":"Inclure le conseil de vente associée. False = le bloc est retiré et le champ reste null.","default":true},"instructions":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Instructions","description":"Instructions libres additionnelles injectées dans le prompt de réponse."},"stream":{"type":"boolean","title":"Stream","description":"true (POST seulement) = réponse en Server-Sent Events (text/event-stream) : événements start, status, chunk, done, error. L'événement done porte le même objet AskResponse que la réponse non streamée. false = JSON classique.","default":false}},"type":"object","required":["question"],"title":"AskRequest"},"AskResponse":{"properties":{"id":{"type":"string","title":"Id"},"object":{"type":"string","const":"pharmassistant.answer","title":"Object","default":"pharmassistant.answer"},"created_at":{"type":"string","title":"Created At"},"status":{"type":"string","enum":["ok","clarification_needed"],"title":"Status"},"mode":{"type":"string","enum":["standard","detailed"],"title":"Mode"},"question":{"type":"string","title":"Question"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Titre court de l'échange (généré sur une question sans historique)."},"answer":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Answer","description":"Réponse en markdown standard, nettoyée des marqueurs internes et des blocs discours patient / vente associée (portés par leurs propres champs) ; les puces à polarité y sont réécrites en ✔/✘ lisibles. Null quand status = clarification_needed."},"key_points":{"items":{"$ref":"#/components/schemas/KeyPoint"},"type":"array","title":"Key Points","description":"Puces à polarité de la réponse en version structurée (positive = recommandé, negative = à éviter). Miroir des puces ✔/✘ de answer, pour exploitation machine."},"patient_advice":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Patient Advice","description":"Discours prêt à tenir au patient (bloc « Au comptoir » du chat), séparé de la réponse. Null si absent ou désactivé par la requête."},"associated_sale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Associated Sale","description":"Conseil de vente associée (bloc « Vente associée » du chat), séparé de la réponse. Null si absent ou désactivé par la requête."},"clarification":{"anyOf":[{"$ref":"#/components/schemas/Clarification"},{"type":"null"}],"description":"Présent quand un mot ambigu bloque le routage : reposer la question avec l'une des corrections proposées."},"suggestions":{"items":{"type":"string"},"type":"array","title":"Suggestions","description":"Pistes d'approfondissement proposées."},"tools_used":{"items":{"type":"string"},"type":"array","title":"Tools Used","description":"Bases documentaires interrogées pour cette réponse."},"sources":{"additionalProperties":{"items":{"$ref":"#/components/schemas/SourceRef"},"type":"array"},"type":"object","title":"Sources","description":"Références COMPLÈTES par base interrogée (mêmes clés que tools_used, liste vide si la base n'a produit aucune référence)."},"conversation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Conversation Id","description":"Identifiant de la conversation côté serveur (conv_…) quand la requête portait store=true ou conversation_id : à renvoyer tel quel au tour suivant pour poursuivre l'échange. Null en mode sans stockage."},"usage":{"$ref":"#/components/schemas/Usage"}},"type":"object","required":["id","created_at","status","mode","question","usage"],"title":"AskResponse"},"Clarification":{"properties":{"word":{"type":"string","title":"Word"},"corrections":{"items":{"type":"string"},"type":"array","title":"Corrections"},"message":{"type":"string","title":"Message"}},"type":"object","required":["word","corrections","message"],"title":"Clarification"},"ConversationDeletedResponse":{"properties":{"id":{"type":"string","title":"Id"},"object":{"type":"string","const":"pharmassistant.conversation","title":"Object","default":"pharmassistant.conversation"},"deleted":{"type":"boolean","const":true,"title":"Deleted","default":true}},"type":"object","required":["id"],"title":"ConversationDeletedResponse"},"ConversationDetailResponse":{"properties":{"id":{"type":"string","title":"Id"},"object":{"type":"string","const":"pharmassistant.conversation","title":"Object","default":"pharmassistant.conversation"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At"},"messages":{"items":{"$ref":"#/components/schemas/ConversationMessage"},"type":"array","title":"Messages","description":"Tous les tours, dans l'ordre chronologique."}},"type":"object","required":["id"],"title":"ConversationDetailResponse"},"ConversationListResponse":{"properties":{"object":{"type":"string","const":"list","title":"Object","default":"list"},"data":{"items":{"$ref":"#/components/schemas/ConversationSummary"},"type":"array","title":"Data","description":"Conversations de la clé, la plus récemment active d'abord."}},"type":"object","title":"ConversationListResponse"},"ConversationMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","title":"Content"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["role","content"],"title":"ConversationMessage"},"ConversationSummary":{"properties":{"id":{"type":"string","title":"Id","description":"Identifiant public de la conversation (conv_…)."},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Titre court, généré à l'ouverture."},"message_count":{"type":"integer","title":"Message Count","description":"Nombre de messages (tours user + assistant)."},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Updated At","description":"Dernière activité ; base de la purge automatique (90 jours d'inactivité)."}},"type":"object","required":["id","message_count"],"title":"ConversationSummary","description":"Entrée du listing des conversations gérées côté serveur."},"ErrorInfo":{"properties":{"code":{"type":"string","title":"Code","description":"Code d'erreur machine, stable (ex. daily_limit_reached)."},"message":{"type":"string","title":"Message","description":"Explication lisible en français."},"details":{"anyOf":[{"items":{},"type":"array"},{"type":"null"}],"title":"Details","description":"Présent sur les 422 : détail de validation champ par champ."}},"type":"object","required":["code","message"],"title":"ErrorInfo"},"ErrorResponse":{"properties":{"error":{"$ref":"#/components/schemas/ErrorInfo"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Enveloppe d'erreur unique de l'API, sur TOUS les statuts d'erreur."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","const":"ok","title":"Status","default":"ok"},"service":{"type":"string","title":"Service","default":"pharmassistant-api"},"version":{"type":"string","title":"Version"}},"type":"object","required":["version"],"title":"HealthResponse"},"HistoryTurn":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","maxLength":16000,"minLength":1,"title":"Content"}},"type":"object","required":["role","content"],"title":"HistoryTurn"},"KeyInfoResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"key_hint":{"type":"string","title":"Key Hint"},"pharmacy_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Pharmacy Slug"},"daily_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Limit","description":"Plafond journalier ; null = illimité."},"monthly_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Limit","description":"Plafond mensuel (mois calendaire Paris) ; null = illimité."},"daily_detailed_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Detailed Limit","description":"Sous-plafond journalier du mode detailed ; null = pas de sous-plafond."},"limits_locked":{"type":"boolean","title":"Limits Locked","description":"true = plafonds fixés par PharmAssistant, non modifiables depuis le portail client (contacter PharmAssistant pour les ajuster).","default":false},"scopes":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scopes","description":"Portées accordées (ask, ask:detailed) ; null = toutes."},"active":{"type":"boolean","title":"Active"},"expires_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Expires At","description":"Péremption de la clé ; null = sans expiration."},"created_at":{"type":"string","title":"Created At"},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At"},"daily_requests_used":{"type":"integer","title":"Daily Requests Used"},"daily_detailed_used":{"type":"integer","title":"Daily Detailed Used","default":0},"month_requests_used":{"type":"integer","title":"Month Requests Used","default":0}},"type":"object","required":["id","name","key_hint","pharmacy_slug","active","created_at","daily_requests_used"],"title":"KeyInfoResponse"},"KeyPoint":{"properties":{"polarity":{"type":"string","enum":["positive","negative"],"title":"Polarity"},"text":{"type":"string","title":"Text"}},"type":"object","required":["polarity","text"],"title":"KeyPoint","description":"Puce à polarité de la réponse : positive = recommandé, negative = à éviter."},"SourceRef":{"properties":{"title":{"type":"string","title":"Title"},"url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url","description":"URL publique de la référence ; null pour une base locale sans lien."}},"type":"object","required":["title"],"title":"SourceRef"},"Usage":{"properties":{"elapsed_ms":{"type":"integer","title":"Elapsed Ms"},"daily_requests_used":{"type":"integer","title":"Daily Requests Used"},"daily_requests_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Daily Requests Limit","description":"Plafond journalier de la clé ; null = illimité."}},"type":"object","required":["elapsed_ms","daily_requests_used"],"title":"Usage"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"CleApi":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Clé d'API pa_… (également acceptée via Authorization: Bearer pa_…)."}}},"tags":[{"name":"Questions","description":"Poser une question au moteur documentaire et récupérer la réponse structurée."},{"name":"Conversations","description":"Conversations gérées côté serveur (créées par un ask avec store=true) : listing, relecture, suppression. Purge automatique après 90 jours d'inactivité."},{"name":"Clé d'API","description":"État de la clé utilisée : plafonds, portées, consommation."},{"name":"Service","description":"Disponibilité du service, sans authentification."}]}