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.

ParameterTypeRequiredDescription
cisstringone of the twoSubject as a French CIS code - exactly 8 digits (BDPM)
ndcstringone of the twoSubject as a US product NDC labeler-product (e.g. 0002-3227)
withstringnoComma-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; 1234567 is a malformed CIS, a 400 - 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.

CaseResult
Known subject200, billed - four sections evaluated, aggregated provenance
Well-formed subject unknown to the register200, billed - honest empty sections (found: false) - the referential absence is the answer
Subject key missing / multiple; a with element malformed; more than 50 with elements400 INVALID_INPUT, not billed
The subject’s register not ingested yet503 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:

SectionBody shape / detail docOwn provenance source
medicationGET /medication/resolveBDPM (FR) or openFDA + RxNorm (US), snapshot
genericsGET /medication/generics, boundedBDPM, snapshot
availabilityGET /medication/availabilityANSM — disponibilité des médicaments, snapshot
interactionsPOST /medication/interactions, subject-centeredThésaurus ANSM, snapshot

Every section carries a small header before its body:

FieldTypeDescription
sourceobject{ name, as_of } - the section’s own source and date
evaluatedboolfalse when nothing was consulted for this section (see coverage.not_evaluated[])
notestringPresent 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 its presentations (the price / reimbursement fact of the medication you asked about).
  • generics - the shape of /medication/generics, bounded: the presentations of the group members are elided, and the note points to the dedicated endpoint for the full detail. The group id, label and members (with is_query) are kept - the substitution picture without the box-level price list. A member the BDPM specialities file no longer carries keeps its identity marking and is counted in the section’s coverage, exactly as on the dedicated endpoint.
  • availability - the shape of /medication/availability (data.ANSM). no_known_shortage is not “in stock”; the veracity note (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.

FieldTypeDescription
with_providedobject[]Interacting pairs involving the subject only - item 0 is the subject, item i is with[i-1]
internalobject[]Interacting pairs among the subject’s own substances (a multi-substance medication)
evaluated_pairsintegerSubject × with pairs actually compared (0 when the subject did not resolve)
internal_evaluated_pairsintegerPairs among the subject’s own substances actually compared
coverageobjectThe 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
disclaimerstringThe 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:

ReasonMeaning
fr_only_scopeThe 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_ingestedA 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 cis or a US ndc, exactly one. No search by symptom, indication or fuzzy name (v1, whole family).
  • with items carry a cis / ndc / dci - an ANSM class as a with element is out of scope here; post it to /medication/interactions, which accepts a class key.
  • The interaction scan is subject-centered - with × with pairs 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 disclaimer states 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.

StatuscodeCase
400INVALID_INPUTZero 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
503DATA_UNAVAILABLEThe 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)
404NOT_FOUNDUnknown route
500INTERNALInternal 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:

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 /catalog and how settlement works.