Accè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. Les clés du contrat
sont en anglais, les contenus en français.
Base : https://api.pharmassistant.fr · Spécification
machine : openapi.json
Authentification
Toutes les routes, sauf /v1/health, exigent une clé d'API au format
pa_…, transmise au choix :
En-tête
Exemple
X-API-Key
X-API-Key: pa_votre_cle
Authorization
Authorization: Bearer pa_votre_cle
Les clés sont délivrées par l'équipe PharmAssistant
(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. La génération de la clé requiert l'acceptation
des conditions
d'utilisation de l'API et de la
politique de
confidentialité. Ne transmettez jamais de données permettant d'identifier un patient
dans vos requêtes.
Démarrage rapide
# Une question, une réponse structurée
curl "https://api.pharmassistant.fr/v1/ask" \
-H "X-API-Key: pa_votre_cle" \
-H "Content-Type: application/json" \
-d '{"question": "Amoxicilline et allaitement ?"}'
# Ou en un seul GET, pour un test immédiat sans corps JSON
curl "https://api.pharmassistant.fr/v1/ask?q=amoxicilline+et+allaitement" \
-H "X-API-Key: pa_votre_cle"
# Conversation côté serveur : "store": true à l'ouverture…
curl "https://api.pharmassistant.fr/v1/ask" \
-H "X-API-Key: pa_votre_cle" -H "Content-Type: application/json" \
-d '{"question": "Amoxicilline et allaitement ?", "store": true}'
# → la réponse porte "conversation_id": "conv_…"
# …puis conversation_id à chaque tour suivant (historique rechargé par le serveur)
curl "https://api.pharmassistant.fr/v1/ask" \
-H "X-API-Key: pa_votre_cle" -H "Content-Type: application/json" \
-d '{"question": "Et pendant la grossesse ?", "conversation_id": "conv_…"}'
from pharmassistant import PharmAssistant
client = PharmAssistant("pa_votre_cle") # ou variable PHARMASSISTANT_API_KEY
# Une question, une réponse structurée
reponse = client.ask("Amoxicilline et allaitement ?", store=True)
print(reponse["answer"]) # markdown propre
print(reponse["patient_advice"]) # discours prêt pour le patient
# Poursuivre la conversation : le serveur porte l'historique
suite = client.ask("Et pendant la grossesse ?",
conversation_id=reponse["conversation_id"])
print(suite["answer"])
Toute erreur de l'API lève PharmAssistantError
(attributs status, code, message), avec les codes
machine de la section Erreurs.
Client officiel mono-fichier, aucune dépendance (fetch
natif), types complets du contrat inclus. Node.js 18+, Deno ou Bun, côté serveur
(ne jamais embarquer la clé dans un navigateur) ; utilisable aussi en JavaScript
après compilation :
import { PharmAssistant } from "./pharmassistant";
const client = new PharmAssistant("pa_votre_cle"); // ou variable PHARMASSISTANT_API_KEY
// Une question, une réponse structurée (types complets du contrat)
const reponse = await client.ask("Amoxicilline et allaitement ?", { store: true });
console.log(reponse.answer); // markdown propre
console.log(reponse.patient_advice); // discours prêt pour le patient
// Poursuivre la conversation : le serveur porte l'historique
const suite = await client.ask("Et pendant la grossesse ?",
{ conversationId: reponse.conversation_id! });
console.log(suite.answer);
Toute erreur de l'API rejette PharmAssistantError
(propriétés status, code, message), avec les codes
machine de la section Erreurs.
PharmAssistant client = new PharmAssistant("pa_votre_cle");
// Une question, une réponse structurée
Map<String, Object> reponse = client.ask("Amoxicilline et allaitement ?",
new PharmAssistant.AskOptions().store(true));
System.out.println(reponse.get("answer")); // markdown propre
System.out.println(reponse.get("patient_advice")); // discours prêt pour le patient
// Poursuivre la conversation : le serveur porte l'historique
Map<String, Object> suite = client.ask("Et pendant la grossesse ?",
new PharmAssistant.AskOptions().conversationId((String) reponse.get("conversation_id")));
System.out.println(suite.get("answer"));
Les réponses sont des Map<String, Object> fidèles
au JSON du contrat ; toute erreur lève PharmAssistant.ApiException
(status, code), avec les codes machine de la section Erreurs.
Exemple de réponse (abrégée) :
{
"id": "req_9f2c4a1b6d8e3f07",
"object": "pharmassistant.answer",
"created_at": "2026-07-23T14:05:12+00:00",
"status": "ok",
"mode": "standard",
"question": "Amoxicilline et allaitement ?",
"title": "Amoxicilline pendant l'allaitement",
"answer": "L'amoxicilline est compatible avec l'allaitement…",
"key_points": [
{"polarity": "positive", "text": "Compatible avec l'allaitement (passage lacté très faible)."},
{"polarity": "negative", "text": "Surveiller diarrhée ou éruption chez le nourrisson."}
],
"patient_advice": "Vous pouvez poursuivre l'allaitement normalement…",
"associated_sale": null,
"clarification": null,
"suggestions": ["Conduite à tenir en cas de candidose sous antibiotique ?"],
"tools_used": ["lecrat_search", "lactmed_search"],
"sources": {
"lecrat_search": [{"title": "CRAT : amoxicilline", "url": "https://www.lecrat.fr/…"}],
"lactmed_search": [{"title": "LactMed : Amoxicillin", "url": "https://www.ncbi.nlm.nih.gov/…"}]
},
"conversation_id": "conv_9a8b7c6d5e4f3a2b",
"usage": {"elapsed_ms": 6480, "daily_requests_used": 12, "daily_requests_limit": 200}
}
Conversations
Trois façons de porter l'état d'un échange, au choix :
Sans état (défaut) : chaque ask est indépendant,
aucune conversation n'est créée côté serveur.
Historique côté client : le champ history porte les
tours précédents à chaque requête ; aucune conversation n'est créée côté serveur.
Conversation côté serveur : "store": true à
l'ouverture crée une conversation et la réponse porte son
conversation_id (conv_…). Les tours suivants n'envoient plus
que question + conversation_id : l'historique est rechargé
par le serveur avec les mêmes règles de reconstruction que le chat, la qualité des
suivis est garantie par construction.
history est exclusif de conversation_id et de
store (422 sinon). GET /v1/conversations liste les
conversations de la clé, GET / DELETE /v1/conversations/{id}
relit ou supprime.
Rétention : une conversation inactive depuis
90 jours est purgée automatiquement (RGPD) ; la suppression explicite est immédiate et
définitive. Comme sur le chat, n'y stockez jamais de données identifiant un patient.
Journaux techniques : indépendamment de ces
trois modes, le contenu des requêtes et des réponses est journalisé côté serveur pendant
30 jours (exploitation, sécurité, support), comme pour le chat. C'est une
raison de plus de ne jamais transmettre de données identifiant un patient.
Streaming (SSE)
"stream": true (POST seulement) fait passer la réponse en
Server-Sent Events (text/event-stream) : la réponse
s'affiche au fil de la génération, et l'événement final done porte le même
objet complet que la réponse non streamée (champs structurés, sources, usage).
curl -N "https://api.pharmassistant.fr/v1/ask" \
-H "X-API-Key: pa_votre_cle" -H "Content-Type: application/json" \
-d '{"question": "Amoxicilline et allaitement ?", "stream": true}'
event: start
data: {"id": "req_9f2c4a1b6d8e3f07", "mode": "standard", "conversation_id": null, …}
event: status
data: {"state": "tools_running", "tools": ["lecrat_search", "lactmed_search"]}
event: chunk
data: {"text": "L'amoxicilline est compatible avec l'allaitement…"}
event: done
data: { …le même objet complet que la réponse non streamée… }
Événement
Contenu
start
Ouverture : id (= X-Request-Id), mode, question, conversation_id éventuel.
status
Champ state : routing (choix des bases), tools_running (+ liste tools, mêmes clés que tools_used), generating (rédaction).
chunk
Champ text : fragment de la réponse markdown, déjà nettoyé (les blocs discours patient / vente associée et les points clés arrivent structurés dans done).
done
L'objet AskResponse complet, identique au mode non streamé. Toujours le dernier événement d'un flux réussi.
error
L'enveloppe d'erreur habituelle ; la requête est remboursée. Les erreurs de pré-vol (authentification, portées, quotas, conversation inconnue) restent des statuts HTTP classiques, avant tout événement.
Avec les clients officiels (Python, TypeScript, Java) :
for event, data in client.ask_stream("Amoxicilline et allaitement ?"):
if event == "chunk":
print(data["text"], end="", flush=True)
elif event == "done":
reponse = data # le même JSON complet que client.ask()
for await (const { event, data } of client.askStream("Amoxicilline et allaitement ?")) {
if (event === "chunk") process.stdout.write(data.text);
else if (event === "done") reponse = data; // le même JSON complet que client.ask()
}
Map<String, Object> reponse = client.askStream("Amoxicilline et allaitement ?",
new PharmAssistant.AskOptions(),
(event, data) -> { if (event.equals("chunk")) System.out.print(data.get("text")); });
Le streaming se combine librement avec les conversations
(store / conversation_id) et le mode detailed.
Modes de réponse
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).
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.
Quotas et en-têtes
La facturation compte des requêtes, pas des tokens. Chaque réponse porte des
en-têtes de pilotage :
En-tête
Contenu
X-RateLimit-Limit
Plafond journalier de la clé (absent si la clé est illimitée).
X-RateLimit-Remaining
Requêtes restantes aujourd'hui.
X-RateLimit-Reset
Horodatage (epoch) du prochain minuit, heure de Paris.
X-Request-Id
Identifiant unique de la requête, à joindre à toute demande de support.
Retry-After
Sur un 429 rate_limited : secondes à attendre avant de réessayer.
GET /v1/me renvoie l'état complet de la clé :
plafonds, portées, consommation du jour et du mois.
Endpoints
POST/v1/askclé requise
Poser une question (JSON)
Forme nominale : JSON, seul question est obligatoire. Avec
stream: true, la réponse passe en Server-Sent Events (text/event-stream) :
événements start, status ({state: routing | tools_running | generating}),
chunk ({text}), puis done (l'objet AskResponse complet, identique au mode
non streamé) ou error (enveloppe d'erreur habituelle, quota remboursé).
Les erreurs de pré-vol (auth, portée, quotas, conversation inconnue) restent
des statuts HTTP classiques.
Clé d'API absente, invalide, révoquée ou expirée (expired_api_key). → ErrorResponse
403
Pharmacie liée à la clé introuvable ou suspendue (pharmacy_not_found / pharmacy_suspended), ou portée manquante sur la clé (scope_denied). → ErrorResponse
404
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. → ErrorResponse
422
Corps ou paramètres invalides (détail champ par champ dans error.details). → ErrorResponse
429
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. → ErrorResponse
500
Erreur interne. Requête non décomptée (quota remboursé). → ErrorResponse
502
Échec de génération ou réponse vide du moteur. Requête non décomptée (quota remboursé), réessayer. → ErrorResponse
GET/v1/askclé requise
Poser une question (test rapide, paramètre q)
Forme minimale : GET /v1/ask?q=… (test rapide au curl/navigateur, sans corps
JSON). Les paramètres optionnels reprennent ceux du POST : store=true ouvre une
conversation côté serveur, conversation_id=conv_… la poursuit (l'historique
client, lui, n'existe qu'en POST).
Clé d'API absente, invalide, révoquée ou expirée (expired_api_key). → ErrorResponse
403
Pharmacie liée à la clé introuvable ou suspendue (pharmacy_not_found / pharmacy_suspended), ou portée manquante sur la clé (scope_denied). → ErrorResponse
404
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. → ErrorResponse
422
Corps ou paramètres invalides (détail champ par champ dans error.details). → ErrorResponse
429
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. → ErrorResponse
500
Erreur interne. Requête non décomptée (quota remboursé). → ErrorResponse
502
Échec de génération ou réponse vide du moteur. Requête non décomptée (quota remboursé), réessayer. → ErrorResponse
GET/v1/conversationsclé requise
Lister mes conversations
Conversations gérées côté serveur de la clé (créées par un ask avec
store=true), la plus récemment active d'abord. limit : 1 à 200 (défaut 50).
Toutes les erreurs partagent la même enveloppe
{"error": {"code", "message", "details"}} avec un code machine
stable. Une requête refusée ou échouée (429, 500, 502) n'est jamais décomptée.
Statut
Code
Quand
401
missing_api_key
Aucune clé fournie dans les en-têtes.
401
invalid_api_key
Clé inconnue ou au mauvais format.
401
revoked_api_key
Clé révoquée.
401
expired_api_key
Clé arrivée à péremption (date configurée sur la clé).
403
scope_denied
La clé n'a pas la portée demandée (ex. ask:detailed).
403
pharmacy_not_found
Pharmacie liée à la clé introuvable.
403
pharmacy_suspended
Pharmacie suspendue.
404
conversation_not_found
conversation_id inconnu pour cette clé (id invalide, conversation supprimée ou purgée après 90 jours d'inactivité). Requête non décomptée.
422
invalid_request
Corps ou paramètres invalides (détail champ par champ dans error.details).
429
rate_limited
Rafale trop rapprochée : réessayer après l'en-tête Retry-After.
429
daily_limit_reached
Plafond journalier de la clé atteint (reset à minuit, heure de Paris).
429
monthly_limit_reached
Plafond mensuel atteint (reset au 1er du mois, heure de Paris).
429
detailed_limit_reached
Sous-plafond journalier du mode detailed atteint.
500
internal_error
Erreur interne du serveur.
502
generation_failed
Échec de la génération par le moteur.
502
empty_response
Réponse vide du moteur.
Schémas
Objets JSON du contrat, tels que renvoyés ou attendus par l'API.
AskRequest
Champ
Type
Description
questionrequis
string
Question du pharmacien, en français. max. 4000 car.
Tours précédents de la conversation quand le CLIENT porte l'état (mode sans stockage). Exclusif de conversation_id et de store. max. 24 éléments
conversation_id
string | null
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. max. 40 car.
store
boolean
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. défaut : false
Autoriser les pistes d'approfondissement (mode standard seulement). défaut : true
patient_advice
boolean
Inclure le discours patient (bloc « Au comptoir »). False = le bloc est retiré et le champ reste null. défaut : true
associated_sale
boolean
Inclure le conseil de vente associée. False = le bloc est retiré et le champ reste null. défaut : true
instructions
string | null
Instructions libres additionnelles injectées dans le prompt de réponse. max. 2000 car.
stream
boolean
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. défaut : false
HistoryTurn
Champ
Type
Description
rolerequis
"user" | "assistant"
contentrequis
string
max. 16000 car.
AskResponse
Champ
Type
Description
idrequis
string
object
"pharmassistant.answer"
défaut : "pharmassistant.answer"
created_atrequis
string
statusrequis
"ok" | "clarification_needed"
moderequis
"standard" | "detailed"
questionrequis
string
title
string | null
Titre court de l'échange (généré sur une question sans historique).
answer
string | null
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.
Puces à polarité de la réponse en version structurée (positive = recommandé, negative = à éviter). Miroir des puces ✔/✘ de answer, pour exploitation machine.
patient_advice
string | null
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
string | null
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.
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
string | null
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.