# 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. Published 2026-08-10 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/a-full-medication-report-for-one-cis-in-one-settlement --- 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`](/docs/api/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`](/docs/api/medication-resolve), [`/medication/generics`](/docs/api/medication-generics), [`/medication/availability`](/docs/api/medication-availability), [`/medication/interactions`](/docs/api/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: ```json { "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](https://ansm.sante.fr/documents/reference/thesaurus-des-interactions-medicamenteuses-1)). 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`](/blog/check-a-prescription-for-interactions-before-an-agent-acts). 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`](/docs/api/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): ```json { "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`](/docs/api/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`](/docs/api/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 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 arrays** — `not_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](/docs/quickstart); the discovery surfaces are described in [For agents](/docs/for-agents). Every amount and every accepted rail lives in the gateway's [`/catalog`](https://api.invoket.com/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`](/docs/api/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](/docs/api/medication-report).