GET /medication/report
Everything an agent needs to know about a medication before acting on a prescription or an order, in one paid call: official identity and price (BDPM), generic group (BDPM), reported shortages (data.ANSM) and the drug-drug interactions of the ANSM thesaurus between the subject and an optional list of co-medications - four sections, each carrying its own source and date. This is the showcase of the “Medication” family and the top of the pre-action checks suite on the prescription side: the four unit endpoints consolidated into a single settlement, so an agent checks a medication in one call instead of four. The answer is served from local stores built from those official datasets, so a report resolves with no network call and no secret at request time.
The suite as one flow. Each unit endpoint remains callable on its own when
an agent needs a single brick - /medication/resolve
(identity) → /medication/generics
(substitution) → /medication/availability
(shortage) → /medication/interactions
(the differentiator) → /medication/report (the synthesis). The report bundles
all four and, where a section would be large, bounds it and points back to
its dedicated endpoint. See the live /catalog for the
authoritative endpoint listing and price.
This endpoint returns sourced, dated facts - authorization labels, prices,
ANSM shortage statuses and the interaction level, management and mechanism
relayed exactly as the ANSM published them. It is never a personalized
clinical opinion, a dosage, or a risk score. Every report carries a
disclaimer saying exactly that. That line is the boundary of the whole
family, and it is at the front of this page on purpose.
Provenance is multi-source and per section
The central editorial point. Each of the four sections carries its own
source { name, as_of } and its own evaluated flag - the sections are
dated differently (BDPM is a monthly snapshot, data.ANSM a dated extraction,
the interactions thesaurus an ANSM publication dated 2024-06-07), and the
response says so, section by section. The root provenance aggregates the
datasets that were actually consulted, and its as_of is the oldest
snapshot consulted - the report is never presented fresher than its oldest
piece of data. Any section that was not evaluated is named in
coverage.not_evaluated[] with a closed reason - never an error, never a
silent absence.
Parameters
The subject is exactly one of two keys. An optional with= adds the
co-medications to check for interactions against the subject.
| Parameter | Type | Required | Description |
|---|---|---|---|
cis | string | one of the two | Subject as a French CIS code - exactly 8 digits (BDPM) |
ndc | string | one of the two | Subject as a US product NDC labeler-product (e.g. 0002-3227) |
with | string | no | Comma-separated list of co-medications (at most 50 elements), each element auto-typed by closed rules (below) |
GET /medication/report?cis=60441622&with=simvastatine
GET /medication/report?cis=60441622&with=simvastatine,0071-0155,60887980
Zero or several subject keys is a 400 INVALID_INPUT (provide exactly one of: cis, ndc).
with= is a comma-separated list, each element auto-typed
Every element of with= is typed by closed, deterministic rules - the key
is never guessed from context:
- all digits → a CIS (must be exactly 8 digits;
1234567is a malformed CIS, a400- it is never requalified as a DCI); - digits with a dash → a NDC (malformed →
400); - otherwise → a DCI substance name (a DCI is never entirely numeric, so there is no ambiguity).
Pass with= once. Repeating the parameter (with=a&with=b, the competing
convention for lists in a query string) is not supported: it is a 400
whose message restates the comma form and gives an example (repeated "with" parameter is not supported; pass a single comma-separated list, e.g. with=clarithromycine,itraconazole).
An empty list or an empty element is a 400, and so is a list of more than
50 elements (the message states the limit): every element is paired against
the subject, so the cost grows with the list while the price stays flat - the
same bound as the items of
/medication/interactions. This mirrors
the item keys of
/medication/interactions - the report
just accepts them as a flat CSV rather than a typed array.
The public POST is a flat-body mirror
The gateway exposes a single GET entry. A POST through the gateway goes
through its flat-body mirror: a flat JSON object
{"cis": "60441622", "with": "simvastatine"} (same comma-separated with) is
rewritten into the query - same expressive power as the GET. A nested
body such as {"with": [{"dci": "simvastatine"}]} sent through the gateway
returns the mirror’s guiding 400 (a flat JSON object is expected). There is
no typed public POST on this route - use the GET form (or its flat
mirror).
x402 golden rule and the status matrix
The agent pays for the report that ran, in a single settlement - including one where a secondary section degrades honestly. Only requests the service cannot answer leave the 200 range, and none of those are billed.
| Case | Result |
|---|---|
| Known subject | 200, billed - four sections evaluated, aggregated provenance |
| Well-formed subject unknown to the register | 200, billed - honest empty sections (found: false) - the referential absence is the answer |
Subject key missing / multiple; a with element malformed; more than 50 with elements | 400 INVALID_INPUT, not billed |
| The subject’s register not ingested yet | 503 DATA_UNAVAILABLE, not settled (see below) |
The 503 is about the subject’s own register only (BDPM for a cis,
openFDA/RxNorm for an ndc). A secondary dataset being absent from the
store (the thesaurus, data.ANSM, or the US store when the subject is a French
cis) does not fail the report - it degrades that section to
not_evaluated with a closed reason, never a 5xx. A missed refresh keeps
serving the last good snapshot with its honest as_of - staleness is never
turned into a 5xx.
200 response - UnifiedResponse
{
"data": {
"subject": { "cis": "60441622" },
"medication": { "source": { "…": "own source + as_of" }, "evaluated": true, "…": "exact shape of /medication/resolve" },
"generics": { "source": { "…": "own source + as_of" }, "evaluated": true, "note": "…", "…": "bounded shape of /medication/generics" },
"availability": { "source": { "…": "own source + as_of" }, "evaluated": true, "…": "exact shape of /medication/availability" },
"interactions": { "source": { "…": "own source + as_of" }, "evaluated": true, "…": "subject-centered interactions" },
"coverage": { "not_evaluated": [], "note": "…" }
},
"provenance": {
"source": "BDPM + ANSM — disponibilité des médicaments (ansm.sante.fr) + Thésaurus ANSM",
"fetched_at": "2026-07-20T05:31:49Z",
"freshness": { "kind": "snapshot", "as_of": "2024-06-07T00:00:00Z" }
}
}
data carries subject (an echo of the resolved key), the four sections, and
a global coverage. Each section has exactly the shape of its unit
endpoint - so the field-level documentation of a section is the
documentation of the corresponding endpoint - wrapped in a small header:
| Section | Body shape / detail doc | Own provenance source |
|---|---|---|
medication | GET /medication/resolve | BDPM (FR) or openFDA + RxNorm (US), snapshot |
generics | GET /medication/generics, bounded | BDPM, snapshot |
availability | GET /medication/availability | ANSM — disponibilité des médicaments, snapshot |
interactions | POST /medication/interactions, subject-centered | Thésaurus ANSM, snapshot |
Every section carries a small header before its body:
| Field | Type | Description |
|---|---|---|
source | object | { name, as_of } - the section’s own source and date |
evaluated | bool | false when nothing was consulted for this section (see coverage.not_evaluated[]) |
note | string | Present where a section is bounded or scoped (e.g. the elided generics detail) |
Section-specific rules
medication- the shape of/medication/resolve(BDPM identity or openFDA/RxNorm, active substances normalized to the INN - the FR↔US bridge). Only the subject keeps itspresentations(the price / reimbursement fact of the medication you asked about).generics- the shape of/medication/generics, bounded: thepresentationsof the group members are elided, and thenotepoints to the dedicated endpoint for the full detail. The group id, label and members (withis_query) are kept - the substitution picture without the box-level price list. A member the BDPM specialities file no longer carries keeps itsidentitymarking and is counted in the section’scoverage, exactly as on the dedicated endpoint.availability- the shape of/medication/availability(data.ANSM).no_known_shortageis not “in stock”; the veracitynote(the ANSM only records reported shortages) travels inside the section.interactions- subject-centered (see next section).
The interactions section is subject-centered
This is the semantic to read most carefully. Inside the report, the interaction
scan is anchored on the subject - it is not a full all-pairs scan of the
with list.
| Field | Type | Description |
|---|---|---|
with_provided | object[] | Interacting pairs involving the subject only - item 0 is the subject, item i is with[i-1] |
internal | object[] | Interacting pairs among the subject’s own substances (a multi-substance medication) |
evaluated_pairs | integer | Subject × with pairs actually compared (0 when the subject did not resolve) |
internal_evaluated_pairs | integer | Pairs among the subject’s own substances actually compared |
coverage | object | The same coverage contract as /medication/interactions: unresolved_items[] (an identifier - subject or with - that resolved to nothing, with a typed reason: unknown_cis / unknown_ndc / unknown_dci / unknown_class) and unevaluated_substances[] (a substance of a resolved medication that is outside the thesaurus); complete: false as soon as either is non-empty - never a fake all-clear |
disclaimer | string | The factual, non-prescriptive relay statement - in every report |
Each entry of with_provided / internal has exactly the shape of an
interactions[] entry on /medication/interactions:
a / b protagonists with matched and ansm_protagonist (the class-member
mechanic: warfarin → ANTIVITAMINES K), a level + English level_label
sorted by decreasing severity, and management / mechanism verbatim ANSM
(omitted when the ANSM published none). A couple the ANSM published under
both of its protagonists is relayed under both.
That includes applies_to: when the ANSM splits a couple into several lines,
each restricted to a precise member of a class, every line carries its scope
verbatim (and coverage.notes says so). Read it before applying level -
see couples the ANSM splits by class member.
Pairs between two with elements are not evaluated here. The report only
crosses the subject against the list and the subject against itself - the
with × with pairs are the job of the dedicated
/medication/interactions endpoint (post
the whole list there for the full all-pairs matrix). The section note says so.
Global coverage - closed reasons, never an error
coverage.not_evaluated[] names every section that was not assessed, each
with a closed reason:
| Reason | Meaning |
|---|---|
fr_only_scope | The subject is a US ndc: generics and availability are BDPM / data.ANSM notions, out of FR scope - a 200 honest statement, not an error |
dataset_not_ingested | A secondary dataset is absent from the store - the section degrades, the report is still served (never a 5xx) |
A section listed in not_evaluated is a fact stated, never a silent gap.
Each evaluated section still carries its own coverage for partial
normalization (e.g. an unmapped substance label).
Example - full report, a real ANSM precaution
GET /medication/report?cis=60441622&with=simvastatine (AMIODARONE VIATRIS
200 mg × simvastatine - the ANSM precaution for use), against the 2026-07-18
BDPM / data.ANSM stores and the 2024-06-07 ANSM thesaurus. The full response is
complete; the salient points are shown here:
{
"data": {
"subject": { "cis": "60441622" },
"medication": {
"source": { "name": "BDPM", "as_of": "2026-07-18" },
"evaluated": true,
"found": true,
"medications": [
{
"country": "FR",
"identifiers": { "cis": "60441622" },
"name": "AMIODARONE VIATRIS 200 mg, comprimé sécable",
"status": "Autorisation active",
"substances": [
{ "id": "amiodarone", "raw_label": "CHLORHYDRATE D'AMIODARONE", "mapped": true }
],
"presentations": [
{ "cip13": "3400934544408", "reimbursement_rate": "65%", "price_cents": 720 }
]
}
]
},
"generics": {
"source": { "name": "BDPM", "as_of": "2026-07-18" },
"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": [
{ "cis": "60441622", "identity": "withdrawn_from_bdpm_specialities", "type": "generique", "is_query": true },
{ "cis": "62202782", "name": "CORDARONE 200 mg, comprimé sécable", "type": "princeps", "is_query": false }
]
}
],
"coverage": {
"members_without_identity": 1,
"note": "1 group member(s) have no name or presentations: the BDPM generic-groups file references CIS codes absent from the BDPM specialities file (marketing authorisation withdrawn); nothing is inferred"
}
},
"availability": {
"source": { "name": "ANSM — disponibilité des médicaments (ansm.sante.fr)", "as_of": "2026-07-18" },
"evaluated": true,
"status": "no_known_shortage",
"signals": [],
"note": "ANSM only records reported shortages; the absence of a report does not guarantee pharmacy stock"
},
"interactions": {
"source": { "name": "Thésaurus ANSM", "as_of": "2024-06-07" },
"evaluated": true,
"note": "with_provided lists pairs involving the subject only (item 0 is the subject, item i is with[i-1]); internal lists pairs among the subject's own substances; with×with pairs are not evaluated here - post the full list to /medication/interactions",
"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) […]",
"source": "Thésaurus ANSM"
}
],
"internal": [],
"evaluated_pairs": 1,
"internal_evaluated_pairs": 0,
"coverage": {
"complete": true,
"unresolved_items": [],
"notes": "Absence of interactions is asserted only for the evaluated pairs; unresolved items and unevaluated substances were not compared."
},
"disclaimer": "Factual relay of the French ANSM interactions thesaurus (level and management as published); not personalized medical advice."
},
"coverage": {
"not_evaluated": [],
"note": "sections listed in not_evaluated were not assessed; each evaluated section carries its own coverage for partial normalization"
}
},
"provenance": {
"source": "BDPM + ANSM — disponibilité des médicaments (ansm.sante.fr) + Thésaurus ANSM",
"fetched_at": "2026-07-20T05:31:49Z",
"freshness": { "kind": "snapshot", "as_of": "2024-06-07T00:00:00Z" }
}
}
Three things to read. Provenance per section: three datasets, two snapshot
dates - and the root as_of is 2024-06-07, the oldest snapshot
consulted (the thesaurus, older than the 2026-07-18 BDPM / data.ANSM images) -
never smoothed to the freshest. The bounded generics: the group and its
members are there, but the members’ presentations are elided, with a note
pointing to /medication/generics for the
full price list. The subject-centered interaction: the AMIODARONE ×
SIMVASTATINE precaution for use is a subject × with pair
(evaluated_pairs: 1), relayed with the ANSM’s own French management and
mechanism.
Example - a well-formed but unknown subject is a paid answer
GET /medication/report?cis=99999999 - a well-formed CIS the register does not
carry - returns a 200, billed, with honest empty sections:
{
"data": {
"subject": { "cis": "99999999" },
"medication": { "source": { "name": "BDPM", "as_of": "2026-07-18" }, "evaluated": true, "found": false },
"generics": { "source": { "name": "BDPM", "as_of": "2026-07-18" }, "evaluated": true, "found": false, "groups": [] },
"availability": { "source": { "name": "ANSM — disponibilité des médicaments (ansm.sante.fr)", "as_of": "2026-07-18" }, "evaluated": true, "status": "no_known_shortage", "signals": [], "note": "ANSM only records reported shortages; the absence of a report does not guarantee pharmacy stock" },
"interactions": {
"source": { "name": "Thésaurus ANSM", "as_of": "2024-06-07" },
"evaluated": true,
"with_provided": [],
"internal": [],
"evaluated_pairs": 0,
"coverage": { "complete": false, "unresolved_items": [ { "item": 0, "input": "99999999", "reason": "unknown_cis" } ], "notes": "…" },
"disclaimer": "Factual relay of the French ANSM interactions thesaurus (level and management as published); not personalized medical advice."
},
"coverage": { "not_evaluated": [] }
}
}
The agent asked “what is behind this CIS?”, and “the register does not carry
it” is the answer. The subject did not resolve, so interactions.evaluated_pairs
is 0 and its coverage.complete: false names the unresolved item - never a
silent [] read as “safe”. Contrast this with the 400s below, which are
malformed requests and are not billed.
Example - a US NDC subject is honest FR-scope coverage
GET /medication/report?ndc=0002-3227 (a US product, on a store with the US
register ingested) returns a 200, billed: the medication and
interactions sections are evaluated (the interaction thesaurus is reached
through the INN substance bridge), while generics and availability -
BDPM and data.ANSM notions - are named in coverage.not_evaluated[] with
fr_only_scope:
{
"data": {
"subject": { "ndc": "0002-3227" },
"medication": { "source": { "name": "openFDA + RxNorm", "as_of": "…" }, "evaluated": true, "found": true, "medications": [ { "country": "US", "…": "exact shape of /medication/resolve, no French price" } ] },
"generics": { "source": { "name": "BDPM" }, "evaluated": false },
"availability": { "source": { "name": "ANSM — disponibilité des médicaments (ansm.sante.fr)" }, "evaluated": false },
"interactions": { "source": { "name": "Thésaurus ANSM", "as_of": "2024-06-07" }, "evaluated": true, "with_provided": [], "internal": [], "evaluated_pairs": 0, "coverage": { "…": "…" }, "disclaimer": "…" },
"coverage": {
"not_evaluated": [
{ "section": "generics", "reason": "fr_only_scope" },
{ "section": "availability", "reason": "fr_only_scope" }
],
"note": "sections listed in not_evaluated were not assessed; each evaluated section carries its own coverage for partial normalization"
}
}
}
An out-of-FR-scope section is a 200 honest statement, not an error. (On a
store where the US register is not ingested, a ndc subject is a
503 DATA_UNAVAILABLE instead - the subject’s own register is missing, so the
report cannot be built; that is not settled.)
Freshness
Each section carries its own freshness in source.as_of: medication /
generics a monthly BDPM snapshot (or the openFDA dump date for a US
subject), availability a dated data.ANSM extraction, interactions the
ANSM publication date of the thesaurus edition (2024-06-07). The root
provenance aggregates the datasets actually consulted and its as_of is the
oldest of them. A missed refresh keeps serving the last good snapshot with
its honest as_of - staleness is never turned into a 5xx.
Limits
Stated plainly, so the agent knows what the report does and does not claim:
- Subject by identifier only - a French
cisor a USndc, exactly one. No search by symptom, indication or fuzzy name (v1, whole family). withitems carry acis/ndc/dci- an ANSM class as awithelement is out of scope here; post it to/medication/interactions, which accepts aclasskey.- The interaction scan is subject-centered -
with×withpairs are not evaluated here (use the dedicated endpoint for the full matrix). - The four sources are dated differently - which is exactly why provenance is per section, never smoothed.
- Facts, not advice - no pregnancy/breastfeeding guidance, no FAERS, no
dosage, no risk score. The
disclaimerstates sourced facts only.
Errors
Only requests the service cannot answer leave the 200 range - none are
billed. A well-formed but unknown subject is a paid 200 (honest empty
sections), not a 404.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Zero or several subject keys; a malformed subject cis/ndc; a malformed with element (e.g. an all-digit value that is not 8 digits); an empty with list or element; more than 50 with elements; a nested POST body through the gateway |
| 503 | DATA_UNAVAILABLE | The subject’s register has not been ingested yet - BDPM for a cis, openFDA/RxNorm for an ndc (a secondary dataset being absent degrades a section instead) |
| 404 | NOT_FOUND | Unknown route |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "provide exactly one of: cis, ndc", "code": "INVALID_INPUT" }
{ "error": "at most 50 with items per call, got 137", "code": "INVALID_INPUT" }
Attribution
The report is multi-source, each section dated in its own source:
medication/generics- the BDPM (Base de données publique des médicaments), made freely available under article L.161-40-1 of the French Social Security Code; US identity from openFDA and RxNorm, in the public domain (NLM/FDA).availability- data.ANSM (disponibilité des médicaments), the open dataset published by the ANSM on ansm.sante.fr.interactions- the Thésaurus des interactions médicamenteuses — ANSM, the public document published by the ANSM; level, management and mechanism relayed in the ANSM’s original French text, never reworded.
See also
The report is the medication “pre-action checks” suite. Need just one brick?
Each section is its own unit endpoint - see the /catalog:
GET /medication/resolve- the official identity card, price and canonical INN substances of a medication.GET /medication/generics- the reference brand and equivalent generics (full member price list).GET /medication/availability- the ANSM reported shortages of a French medication.POST /medication/interactions- the full all-pairs drug-drug interaction matrix over a list, includingwith×withand ANSM classes.
Then, before acting on the wider order or prescription:
GET /company/resolve- resolve the supplier or pharmacy behind a SIREN before onboarding it.- For agents - discovery surfaces, the live
/catalogand how settlement works.