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

SectionDatasetQuestion it answers
medicationBDPM (FR) — or openFDA + RxNorm (US)What is this product: status, form, holder, substances, price and reimbursement?
genericsBDPM generic groupsWhich products are in the same regulatory substitution group?
availabilitydata.ANSMHas a supply problem been reported for it?
interactionsThésaurus ANSMWhat 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_of equal 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_provided holds pairs involving the subject only — item 0 is the subject, item i is with[i-1].
  • internal holds pairs among the subject’s own substances, for a multi-substance product.
  • Pairs between two with elements are not evaluated here. In the capture above, simvastatin × clarithromycin was never compared, and evaluated_pairs: 2 says 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

CaseResult
Known subject200, billed — four sections, aggregated provenance
Well-formed CIS or NDC unknown to the register200, 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 items400 INVALID_INPUT, not billed
The subject’s own register not ingested503 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_ofstaleness is never turned into a 5xx.

Wiring it up

  1. Anchor on one identifier. Exactly one of cis (8 digits) or ndc. There is no search by name, symptom or indication.
  2. 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 a 400, not a silent list.
  3. Read coverage before reading the arraysnot_evaluated[] at the root, complete and unevaluated_substances inside interactions.
  4. 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_of dates. That is what per-section provenance is for.
  5. 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 disclaimer saying it is a factual relay of published sources. No dosage, no personalised recommendation, no risk score, no pregnancy or breastfeeding guidance.
  • No with × with pairs, and no ANSM class as a with item — both belong to /medication/interactions.
  • No US generics or US shortages. generics and availability are French notions; an NDC subject gets fr_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.