A full medication report for one CIS code, in one call
Identity, generics, shortages and ANSM interactions in one paid call — four sections, four different dates, and a root date that is the oldest of them, never the freshest.
By Matthias Begot · · Updated
An agent about to dispense, substitute, order or summarise a medication asks four
questions that live in three different official datasets: what is this product
exactly, what may legally stand in for it, has a shortage been reported, and does
it interact with what the patient is already taking.
GET /medication/report answers all four for one
French CIS code — or a US NDC — in one settlement. The bundling saves
three calls; the contract is the interesting part: each section carries its own
source and its own date, and the report’s root date is the oldest of them,
never the freshest.
That rule sounds pedantic until you look at what it exposes. One of the four sources has not been updated since September 2023. A composite that averaged its dates, or stamped the response with the time it was generated, would quietly present two-year-old reference data as today’s. This one refuses to.
Four sections, three datasets, three clocks
| Section | Dataset | Question it answers |
|---|---|---|
medication | BDPM (FR) — or openFDA + RxNorm (US) | What is this product: status, form, holder, substances, price and reimbursement? |
generics | BDPM generic groups | Which products are in the same regulatory substitution group? |
availability | data.ANSM | Has a supply problem been reported for it? |
interactions | Thésaurus ANSM | What has an authority published about it against a given co-medication? |
Each section has exactly the shape of its unit endpoint —
/medication/resolve,
/medication/generics,
/medication/availability,
/medication/interactions — so the
field-level documentation of a section is the documentation of that endpoint.
The report adds a small header on each: source { name, as_of }, an evaluated
flag, and a note where the section is bounded.
A capture from production, taken today
GET /medication/report?cis=60441622&with=simvastatine,clarithromycine —
AMIODARONE VIATRIS 200 mg, checked against two co-medications. Abridged from the
real response, captured 2026-08-10:
{
"data": {
"subject": { "cis": "60441622" },
"medication": {
"source": { "name": "BDPM", "as_of": "2026-08-08" },
"evaluated": true, "found": true,
"medications": [{
"country": "FR",
"name": "AMIODARONE VIATRIS 200 mg, comprimé sécable",
"status": "Autorisation active",
"marketing_status": "Commercialisée",
"dispensing": ["liste I"],
"substances": [{ "id": "amiodarone", "raw_label": "CHLORHYDRATE D'AMIODARONE", "mapped": true }],
"presentations": [{ "cip13": "3400934544408", "reimbursement_rate": "65%",
"price_cents": 720, "price_with_dispensing_fee_cents": 822 }]
}]
},
"generics": {
"source": { "name": "BDPM", "as_of": "2026-08-08" },
"evaluated": true,
"note": "member presentations are elided in the report; call /medication/generics for full detail",
"found": true,
"groups": [{ "id": 43, "label": "AMIODARONE (CHLORHYDRATE D') 200 mg - CORDARONE 200 mg, comprimé sécable",
"members": ["… 12 members, princeps + generiques, is_query on the subject …"] }],
"coverage": { "members_without_identity": 3, "note": "…" }
},
"availability": {
"source": { "name": "ANSM — disponibilité des médicaments (ansm.sante.fr)", "as_of": "2026-08-10" },
"evaluated": true,
"status": "no_known_shortage", "signals": [],
"note": "ANSM only records reported shortages; the absence of a report does not guarantee pharmacy stock",
"coverage": { "scope": "FR", "unlinked_signals": 61, "note": "…" }
},
"interactions": {
"source": { "name": "Thésaurus ANSM", "as_of": "2024-06-07" },
"evaluated": true,
"with_provided": [
{ "a": { "item": 0, "matched": "amiodarone", "ansm_protagonist": "AMIODARONE" },
"b": { "item": 1, "matched": "simvastatin", "ansm_protagonist": "SIMVASTATINE" },
"level": "precaution_emploi", "level_label": "precaution for use",
"management": "Ne pas dépasser la posologie de 20 mg/j de simvastatine […]",
"mechanism": "Risque majoré d'effets indésirables […] à type de rhabdomyolyse […]" },
{ "a": { "item": 0, "matched": "amiodarone",
"ansm_protagonist": "SUBSTANCES SUSCEPTIBLES DE DONNER DES TORSADES DE POINTES" },
"b": { "item": 2, "matched": "clarithromycin", "ansm_protagonist": "CLARITHROMYCINE" },
"level": "precaution_emploi", "level_label": "precaution for use",
"management": "Surveillance clinique et électrocardiographique pendant l'association.",
"mechanism": "Risque majoré de troubles du rythme ventriculaire, notamment de torsades de pointes." }
],
"internal": [], "evaluated_pairs": 2, "internal_evaluated_pairs": 0,
"coverage": { "complete": true, "unresolved_items": [], "notes": "…" },
"disclaimer": "Factual relay of the French ANSM interactions thesaurus (level and management as published); not personalized medical advice."
},
"coverage": { "not_evaluated": [], "note": "…" }
},
"provenance": {
"source": "BDPM + ANSM — disponibilité des médicaments (ansm.sante.fr) + Thésaurus ANSM",
"fetched_at": "2026-08-10T20:58:52Z",
"freshness": { "kind": "snapshot", "as_of": "2024-06-07T00:00:00Z" }
}
}
Read the dates before you read the facts. BDPM as of 8 August 2026, data.ANSM
as of 10 August 2026, the interactions thesaurus as of 7 June 2024 — and the
root as_of is 2024-06-07, the oldest of the three. The response was
generated today; it does not claim to be from today.
The root date is the oldest on purpose
The ANSM interactions thesaurus is not merely a slow-moving dataset. It is a closed one. The ANSM’s own reference page states that the version published on 15 September 2023 is the last: « La version publiée le 15 septembre 2023 constitue la dernière version du Thésaurus. Aucune nouvelle actualisation ne sera réalisée par l’ANSM. » The document remains downloadable, and the page says it will stay accessible until 15 June 2027 (ANSM, Thésaurus des interactions médicamenteuses).
So one section of this report is anchored to a frozen authority while the other three refresh weekly. There is no honest way to express that in a single timestamp. Two design choices follow from it:
- Per-section
source.as_of— the caller can see exactly which fact is old and which is current, instead of inferring it. - A root
as_ofequal to the oldest snapshot actually consulted — so a cached or logged report is never read as fresher than its weakest input.
Why the source stands anyway, and what a frozen-but-authoritative reference
means for an agent, is the subject of
the article on /medication/interactions.
The short version: it remains the published French reference, it is relayed
verbatim, and it is dated in every response.
The interaction scan is subject-centered
This is the semantic to read most carefully in the report, because it is narrower than the dedicated endpoint.
with_providedholds pairs involving the subject only — item0is the subject, itemiiswith[i-1].internalholds pairs among the subject’s own substances, for a multi-substance product.- Pairs between two
withelements are not evaluated here. In the capture above, simvastatin × clarithromycin was never compared, andevaluated_pairs: 2says exactly how many comparisons were made.
If you need the full all-pairs matrix over a list — every element against every
other, plus ANSM therapeutic classes as items — post the list to
/medication/interactions. The report’s
section note points there rather than pretending to cover it.
The second interaction in the capture also shows the class mechanic worth
understanding: the subject matched as
SUBSTANCES SUSCEPTIBLES DE DONNER DES TORSADES DE POINTES, not as
AMIODARONE. The ANSM publishes many couples at the level of a therapeutic
class, and ansm_protagonist names the side actually matched, so an agent can
tell a substance-level rule from a class-level one. management and mechanism
are relayed in the ANSM’s original French, never reworded into English or
summarised.
”No interaction found” is not an all-clear, and the response proves it
Ask for a US product and the report degrades in two directions at once, both
stated. GET /medication/report?ndc=0002-3227&with=clarithromycine (Strattera,
atomoxetine):
{
"medication": { "source": { "name": "openFDA + RxNorm", "as_of": "2026-08-07" }, "evaluated": true, "found": true,
"coverage": { "unmapped_substances": ["ATOMOXETINE HYDROCHLORIDE"] } },
"generics": { "evaluated": false, "coverage": { "scope": "FR", "note": "generic groups are a French (BDPM) notion; NDC keys are out of scope in v1" } },
"availability": { "evaluated": false, "coverage": { "scope": "FR", "note": "shortage reports come from data.ANSM (French market); NDC keys are out of scope in v1" } },
"interactions": {
"evaluated": true, "with_provided": [], "evaluated_pairs": 0,
"coverage": { "complete": false, "unresolved_items": [],
"unevaluated_substances": [ { "item": 0, "label": "ATOMOXETINE HYDROCHLORIDE" } ] }
},
"coverage": { "not_evaluated": [ { "section": "generics", "reason": "fr_only_scope" },
{ "section": "availability", "reason": "fr_only_scope" } ] }
}
with_provided is empty. An agent reading only that would conclude “no
interaction between atomoxetine and clarithromycin”. The response says
otherwise: evaluated_pairs: 0, complete: false, and
unevaluated_substances naming the exact substance that falls outside the
thesaurus. Nothing was compared, so nothing is asserted. An empty array plus
an honest coverage block is a different statement from an empty array alone, and
this is the field to branch on.
The two French-only sections are equally explicit: evaluated: false on the
section, and a matching entry in the global coverage.not_evaluated[] with the
closed reason fr_only_scope. That is a paid, honest 200 — a stated
scope limit, not an error and not a silent gap.
Two sections that are deliberately narrower than they look
generics is bounded. The group, its label and all its members are returned
with their official type (princeps / generique) and an is_query flag on
the subject — but the members’ presentations are elided, and the note
points to /medication/generics for the full
box-level price list. The report gives the substitution picture, not the
purchasing sheet. Note too members_without_identity: 3 in the capture: the
BDPM generic-groups file references CIS codes the specialities file no longer
carries (marketing authorisation withdrawn). Those members are counted and
flagged, never inferred.
availability reports declarations, not stock. no_known_shortage means
the ANSM register carries no reported supply problem for this CIS — it is not a
statement that pharmacies have it. The section carries that caveat in its own
note on every response, along with unlinked_signals: reports in the snapshot
that the ANSM published against a product label rather than a CIS code and that
could not be linked to one. In the capture, 61 of them are invisible to a
CIS-keyed query. The count is disclosed rather than dropped; the substance-keyed
form on /medication/availability is how
you reach them.
Billing: an unknown code is still an answer
| Case | Result |
|---|---|
| Known subject | 200, billed — four sections, aggregated provenance |
| Well-formed CIS or NDC unknown to the register | 200, billed — honest empty sections (found: false), with the unresolved subject named in the interactions coverage |
No subject key, two subject keys, a malformed cis/ndc, a malformed or empty with element, more than 50 with items | 400 INVALID_INPUT, not billed |
| The subject’s own register not ingested | 503 DATA_UNAVAILABLE, not settled |
The middle row is the deliberate one. “This CIS is not in the BDPM” is a real finding for an agent parsing a prescription or a supplier line, so it is served and billed rather than returned as a 404. Malformed input is not a question, so it is not billed.
The 503 is scoped to the subject’s register only — BDPM for a cis,
openFDA/RxNorm for an ndc. A secondary dataset missing from the store
degrades its section to evaluated: false with a closed reason; it never fails
the report. And a missed refresh keeps serving the last good snapshot with its
honest as_of — staleness is never turned into a 5xx.
Wiring it up
- Anchor on one identifier. Exactly one of
cis(8 digits) orndc. There is no search by name, symptom or indication. - Pass
with=once, comma-separated. Each element is typed by closed rules: all digits → CIS, digits with a dash → NDC, anything else → an INN substance name. Repeating the parameter is a400, not a silent list. - Read
coveragebefore reading the arrays —not_evaluated[]at the root,completeandunevaluated_substancesinsideinteractions. - Persist the whole response, not a boolean. Six months later, “on what
basis did the agent substitute this product” is answerable from the stored
sections and their
as_ofdates. That is what per-section provenance is for. - Discover, then pay. The
402→ pay → replay cycle is walked with runnable snippets in the Quickstart; the discovery surfaces are described in For agents.
Every amount and every accepted rail lives in the gateway’s
/catalog, which is the single source of
truth; no price appears in this article, and the four unit endpoints are
listed there too if you would rather orchestrate them yourself.
What it will not do
- No clinical advice. Every report carries a
disclaimersaying it is a factual relay of published sources. No dosage, no personalised recommendation, no risk score, no pregnancy or breastfeeding guidance. - No
with×withpairs, and no ANSM class as awithitem — both belong to/medication/interactions. - No US generics or US shortages.
genericsandavailabilityare French notions; an NDC subject getsfr_only_scope. - No date smoothing. Four sources, four dates, root equal to the oldest.
One caveat on the captures above: they are snapshot-dated illustrations, not
a contract. A BDPM refresh moves a price, a new ANSM report moves an
availability status. Only the derivation rules — provenance per section, the
oldest date at the root, coverage stated rather than implied, nothing compared
means nothing asserted — are guaranteed. The parameter contract, the full
section shapes and the error codes are on the
GET /medication/report documentation.