Check a medication for shortages: the French ANSM register

The ANSM register lists declared shortages, not pharmacy stock. That is why /medication/availability answers no_known_shortage and never "available".

By Matthias Begot · · Updated

An agent that is about to order a medication, promise a delivery date or offer a substitute needs to know whether an authority has published a supply problem for it. GET /medication/availability answers that from the official ANSM shortage reports — rupture, risk of shortage, tension, return to market, discontinuation — relayed exactly as published, each with its date, medical domain and official report URL. What it will never return is a status called available. The synthesis status for a medication with no report is named no_known_shortage, and that naming choice is the whole subject of this article: it is the difference between a fact and a guarantee an agent must not infer.

The ANSM register is a register of declarations

The French dataset is built from what pharmaceutical companies declared. Marketing authorisation holders and operators of médicaments d’intérêt thérapeutique majeur (MITM) must report a shortage or a risk of shortage to the ANSM, under article R. 5124-49-1 of the code de la santé publique, through the Trustmed filing platform, and the agency insists they do so as early as possible.

The ANSM says what that makes the published list, on its own page:

These tables do not list all MITM but the medicines that were subject to a declaration of shortage or risk of shortage during the year. — ANSM, Médicaments ayant fait l’objet d’un signalement de rupture

So the register answers exactly one question: has a supply problem been declared to the French agency for this product? It is not an inventory. No public dataset in France carries what is on a pharmacy shelf this morning, and an API that implied otherwise would be inventing the most operationally dangerous field in the response.

no_known_shortage, never available

The endpoint encodes that boundary in the status name itself. A well-formed CIS with no report comes back as a billed 200:

{
  "data": {
    "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 }
  }
}

Three things are doing work here.

  • The status is a statement about the register, not about the product. Read no_known_shortage as “the ANSM has published nothing about this medication”.
  • The note travels with every French answer, not just the empty ones. It is a field rather than a footnote because agents read fields, and the caveat has to survive being passed through a summariser.
  • The empty signals: [] is the answer, not a miss. The agent asked whether a shortage was reported; “none reported” is information, and it is billed as such.

The rule to wire in is one line: no_known_shortage is a green light to keep looking, not a green light to promise a delivery.

Five statuses, relayed exactly as published

When reports do exist, they come back verbatim. status is a closed set of the five categories the ANSM publishes:

StatusWhat the ANSM declared
ruptureShortage — the product cannot be supplied
risque_ruptureA shortage is anticipated
tensionSupply is strained
remise_a_dispositionThe product is back on the market
arret_commercialisationMarketing has been discontinued

A real report, resolved by CIS:

{
  "status": "tension",
  "signals": [
    {
      "cis": "65979151",
      "match_kind": "exact",
      "name": "Lantus Solostar 100 unités/ ml, solution injectable en stylo prérempli",
      "status": "tension",
      "updated_at": "2026-04-02",
      "domain": "Endocrinologie",
      "source_ref": "https://ansm.sante.fr/disponibilites-des-produits-de-sante/medicaments/lantus-solostar-100-unites-ml-solution-injectable-en-stylo-prerempli-insuline-glargine"
    }
  ]
}

Two date fields deserve care, because both are easy to over-read:

  • updated_at is the report’s update date — the only tracking date the source publishes. It is not the date the shortage started; the ANSM table does not carry one, so the service does not invent one.
  • restock_date is a return date as published: observed when the status is remise_a_disposition, forecast otherwise, and omitted when nothing has been announced. A resolved case carries both, as with Quinimax 500 mg — remise_a_disposition, updated_at and restock_date on 2026-07-16.

When a CIS carries several reports, the synthesis status is the status of the most recent one. The service never reorders reports by an invented severity scale — that would be the service forming an opinion.

The bound a CIS lookup cannot see

Here is the second honesty problem, and the reason every cis answer carries a coverage block. The ANSM publishes product labels, not CIS codes. Linking a report to a BDPM code happens at ingestion and is deliberately strict: an exact name match, or a brand + strength prefix matching exactly one BDPM name — match_kind tells you which. Anything ambiguous is left unlinked rather than guessed, because a report about another strength of the same substance is not a shortage of the product you asked about.

Atropine eye drops show what that costs. Asking by substance returns three reports, and none of them carries a cis:

{
  "found": true,
  "items": [
    { "name": "Atropine 0,3%, collyre", "status": "arret_commercialisation", "updated_at": "2026-01-06", "domain": "Ophtalmologie" },
    { "name": "Atropine 1%, collyre",   "status": "tension",                 "updated_at": "2024-10-16", "domain": "Ophtalmologie" },
    { "name": "Atropine 0,5%, collyre", "status": "tension",                 "updated_at": "2024-07-18", "domain": "Ophtalmologie" }
  ]
}

Phenobarbital is the mixed case that makes the mechanism concrete: Gardénal 50 mg, comprimé links to CIS 65391080 with match_kind: "exact", while Alepsal, comprimé — reported on the same day, same domain — links to nothing and comes back with no cis at all.

That is what coverage.unlinked_signals counts: the reports in the current snapshot that carry no CIS and are therefore invisible to a cis query. It is the published bound of what no_known_shortage asserts. To rule them out, ask the same question by substance:

GET /medication/availability?substance=atropine

The substance key searches the published substance names and returns unlinked reports too, with found / items[] instead of status / signals[]. dci is a synonym of substance — pass one, never both. Substance resolution folds through the same canonical key the rest of the family uses, so an English INN reaches the French reports of the same molecule.

Why this is a French dataset, and why that is not a gap in ambition

EU-level shortage reporting has been centralised since the EMA’s European Shortages Monitoring Platform (ESMP), live for routine reporting by marketing authorisation holders on 28 November 2024 and in full scope on 29 January 2025. It is genuinely machine-to-machine — for submission. Reading it requires an EMA account and an ESMP role, and access is tiered by a data-access policy. There is no public dataset behind it.

Open, per-product, dated shortage data that anyone can serve is therefore a national-register story today, and France publishes one. Hence the scope: a US ndc key returns an honest coverage note, never an error and never a guess:

{
  "coverage": {
    "scope": "FR",
    "note": "shortage reports come from data.ANSM (French market); NDC keys are out of scope in v1"
  }
}

That branch consults no store, so provenance.freshness.kind is live and an ndc key can never return a 503. There is no US equivalent in v1, and the endpoint says so rather than pretending.

Facts and dates, never a clinical opinion

The endpoint relays ANSM statuses, dates and report links. It does not recommend a therapeutic alternative, a dosage or a course of action — the same line the whole medication family holds. source_ref on every report points at the official ansm.sante.fr page so a human can verify the claim the agent acted on.

Answers are served from a dated snapshot of the data.ANSM dataset, with no network call and no secret at request time:

"provenance": {
  "source": "ANSM — disponibilité des médicaments (ansm.sante.fr)",
  "freshness": { "kind": "snapshot", "as_of": "..." }
}

as_of names the extraction the answer was built from. A missed refresh keeps serving the last good snapshot, honestly dated — staleness is never turned into a 5xx. A dataset never ingested at all is a 503, because there is nothing to serve, not because the data is old.

What you pay for

The x402 golden rule applies literally: the agent pays for the answer to its question.

  1. Discover the endpoint in the live catalog, call it, receive the 402.
  2. Pay — sign the chosen rail and replay the request.
  3. Read status and coverage together, never status alone.
  4. Branch — proceed, look for an equivalent, or escalate.

A well-formed CIS with no report is a billed 200 (no_known_shortage), and so is a substance with no match (found: false, items: []). Only requests the service cannot answer leave the 200 range, and none of them are billed:

{ "error": "provide exactly one of: cis, substance, dci, ndc", "code": "INVALID_INPUT" }

Zero or several keys, a CIS that is not eight digits, a package-level NDC, an empty substance — all 400, none billed. Quickstart walks the full discover → 402 → pay → replay cycle; price and accepted rails are served live by the catalog, never pinned in an article.

Chain it

A shortage answer is usually the middle of a workflow, not the end. GET /medication/resolve gives the identity card behind the code you are about to check — marketing status, canonical substances, presentations. When a reference brand is short, GET /medication/generics returns its BDPM generic group so the agent can check the equivalents for reports too, and POST /medication/interactions checks the alternative against the rest of the list before it is proposed. For identity, generics, reported shortages and interactions on one medication in a single settlement, use the composite GET /medication/report.

For the full field reference, the coverage semantics and the error codes, see the GET /medication/availability documentation; for how agents discover and call Invoket endpoints, see For agents.