POST /medication/interactions

Returns the drug-drug interactions published between the medications you send, from the official ANSM interactions thesaurus (Thésaurus des interactions médicamenteuses): for each interacting pair, the constraint level, the management to follow and the mechanism relayed exactly as the ANSM wrote them - plus an honest coverage of everything that could not be compared. The answer is served from a local store built from the ANSM thesaurus, so an evaluation resolves in milliseconds with no network call and no secret at request time.

This is the showcase endpoint of the medication family. Since the NLM’s RxNav interaction API was shut down in January 2024, no authoritative drug-drug interaction source exists on x402, and the crowd of openFDA wrappers carries no structured DDI. This is the answer data of the catalog: an agent checks a prescription or an order before acting on it - the top of the “pre-action checks” suite (iban-screen, phone-validate) on the prescription side. US medications join the French thesaurus through the INN substance bridge (the same canonical substance.id as /medication/resolve). See the live /catalog for the authoritative endpoint listing and price.

”No interaction” is firm only for the pairs actually compared

This is the central point of the endpoint - the veracity line. A pair reports interactions: [] only for the substances the service could resolve and compare. An item it could not resolve (an unknown CIS/NDC, an unknown class) or a substance outside the thesaurus scope (a US ingredient not bridged to an INN) goes into the coverage block - never buried in a fake all-clear. So read interactions: [] together with coverage.complete: a true means “every item was resolved and every inter-item pair compared”, a false means “some items were not evaluated - see coverage”.

This endpoint returns sourced facts - the ANSM level, management and mechanism, in the original French text, never reworded or translated. It is never a personalized clinical opinion, a dosage or a risk score. Every response carries a disclaimer saying exactly that. That line is the boundary of the service.

x402 golden rule: the agent pays for the answer to its question. A well-formed list of 2+ items with no known interaction is a successful answer too -> a 200 billed with interactions: [] and coverage.complete: true - the absence of an interaction between evaluated pairs is the information. Only requests the service cannot answer - fewer than 2 items, more than 50, an item with zero or several keys, a malformed key, an invalid country, a non-JSON body - leave the 200 range and are not billed.

Request body

A JSON POST body with 2 to 50 items. Each item carries exactly one of the four keys below - the item is auto-typed by its key. The upper bound exists because every pair is evaluated: the cost grows quadratically while the price is flat, and 50 items (1 225 pairs) already answers far more than a real prescription.

POST /medication/interactions
Content-Type: application/json
{
  "items": [
    { "dci": "warfarine" },
    { "dci": "millepertuis" }
  ],
  "country": "FR"
}
FieldTypeRequiredDescription
itemsobject[]yes2 to 50 items, each with exactly one of the keys below; beyond 50 the 400 states the limit
countrystringnoFR or US (case-insensitive) - validated but does not steer resolution in v1 (each item is auto-typed by its key); an absurd value is a 400, never silently ignored

Each item carries exactly one key:

KeyTypeDescription
cisstringFrench CIS code - exactly 8 digits (BDPM)
ndcstringUS product NDC labeler-product (e.g. 0071-0155) - joins the thesaurus through the INN bridge
dcistringINN/DCI substance name, resolved through the shared canonical substance key (non-empty)
classstringAn ANSM thesaurus class label (e.g. antivitamines K), non-empty

Fewer than 2 items, more than 50, an item with zero or several keys, a malformed CIS/NDC, an empty dci/class, an invalid country, or a non-JSON body is a 400 INVALID_INPUT.

Interaction levels

Every interaction carries a level (closed set, ANSM severity codes) and an English level_label. interactions[] is sorted by decreasing severity - contraindications first.

levellevel_labelMeaning (ANSM)
contre_indicationcontraindicationThe most severe - the association must not be used
association_deconseilleeadvised againstThe association is advised against
precaution_emploiprecaution for useThe association is possible with a precaution for use
a_prendre_en_compteto be taken into accountThe association is to be taken into account

200 response - UnifiedResponse

{
  "data": {
    "interactions": [ ... ],
    "evaluated_pairs": 1,
    "coverage": { ... },
    "disclaimer": "Factual relay of the French ANSM interactions thesaurus (level and management as published); not personalized medical advice."
  },
  "provenance": {
    "source": "Thésaurus ANSM",
    "fetched_at": "2026-07-20T05:31:49Z",
    "freshness": { "kind": "snapshot", "as_of": "2024-06-07T00:00:00Z" }
  }
}

Fields of data

FieldTypeDescription
interactionsobject[]The interactions found between inter-item pairs, sorted by decreasing severity; [] when none
evaluated_pairsintegerThe number of inter-item pairs actually compared - the scope over which “no interaction” is asserted
coverageobjectWhat was and was not evaluated (see below) - carried in every response
disclaimerstringThe factual, non-prescriptive relay statement - carried in every response

Pairs are inter-item only: a marketed combination never flags its own internal association.

An interactions[] entry

FieldTypeDescription
a, bobjectThe two protagonists (see below)
levelstringThe severity code - one of contre_indication / association_deconseillee / precaution_emploi / a_prendre_en_compte
level_labelstringThe English label of the level
managementstringThe management to follow, ANSM verbatim in French; omitted when the ANSM published none
applies_tostringThe scope this line is restricted to, ANSM verbatim in French (e.g. avec les macrolides); omitted when the line applies to the whole couple - see below
mechanismstringThe mechanism, ANSM verbatim in French; omitted when the ANSM published none
sourcestring"Thésaurus ANSM"

Each protagonist (a / b) carries:

FieldTypeDescription
itemintegerThe 0-based index of the item in the list you sent
matchedstringThe canonical substance or class actually compared
ansm_protagoniststringThe couple’s label exactly as published by the ANSM - which may be a class the item’s substance belongs to (e.g. warfarin -> ANTIVITAMINES K)

A field the ANSM did not publish is omitted from the JSON, never served null. A couple the ANSM published under both of its protagonists is relayed under both, exactly as published (see the AMIODARONE × SIMVASTATINE note below).

Example - a contraindication, class-member mechanic

POST /medication/interactions with {"items":[{"dci":"warfarine"},{"dci":"millepertuis"}]}, against the 2024-06-07 ANSM edition. Warfarin is compared through its class ANTIVITAMINES K, and the couple is relayed under both of its protagonists:

{
  "data": {
    "interactions": [
      {
        "a": { "item": 0, "matched": "warfarin", "ansm_protagonist": "ANTIVITAMINES K" },
        "b": { "item": 1, "matched": "millepertuis", "ansm_protagonist": "MILLEPERTUIS" },
        "level": "contre_indication",
        "level_label": "contraindication",
        "management": "En cas d'association fortuite, ne pas interrompre brutalement la prise de millepertuis mais contrôler l'INR avant puis après l'arrêt du millepertuis.",
        "mechanism": "Diminution des concentrations plasmatiques de l'antivitamine K, en raison de son effet inducteur enzymatique, avec risque de baisse d'efficacité voire d'annulation de l'effet dont les conséquences peuvent être éventuellement graves (évènement thrombotique).",
        "source": "Thésaurus ANSM"
      },
      {
        "a": { "item": 0, "matched": "warfarin", "ansm_protagonist": "MILLEPERTUIS" },
        "b": { "item": 1, "matched": "millepertuis", "ansm_protagonist": "ANTIVITAMINES K" },
        "level": "contre_indication",
        "level_label": "contraindication",
        "management": "En cas d'association fortuite, ne pas interrompre brutalement la prise de millepertuis mais contrôler l'INR avant puis après l'arrêt du millepertuis.",
        "mechanism": "Diminution des concentrations plasmatiques de l'antivitamine K, en raison de son effet inducteur enzymatique, avec risque de baisse d'efficacité voire d'annulation de l'effet dont les conséquences peuvent être éventuellement graves (évènement thrombotique).",
        "source": "Thésaurus ANSM"
      }
    ],
    "evaluated_pairs": 1,
    "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."
  },
  "provenance": {
    "source": "Thésaurus ANSM",
    "fetched_at": "2026-07-20T05:31:49Z",
    "freshness": { "kind": "snapshot", "as_of": "2024-06-07T00:00:00Z" }
  }
}

The dci warfarine is a member of the ANSM class ANTIVITAMINES K, so the class-level interaction applies to it: matched is the substance actually compared (warfarin), ansm_protagonist is the label the ANSM published (the class). The management and mechanism are the ANSM’s own French text, untouched.

Sending {"items":[{"cis":"60177044"},{"dci":"simvastatine"}]} (a Cordarone CIS -> amiodarone) returns a 200 with a precaution for use AMIODARONE × SIMVASTATINE, relayed under both of its protagonists (2 entries, evaluated_pairs: 1).

A well-formed list with no known interaction is a paid answer

{"items":[{"dci":"paracetamol"},{"dci":"amoxicilline"}]} returns a 200, billed:

{
  "data": {
    "interactions": [],
    "evaluated_pairs": 1,
    "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."
  }
}

The agent asked “is there a published interaction between these two?”, and “none between the evaluated pair” is the answer - read together with coverage.complete: true, which certifies that both items were resolved and the pair was compared. Contrast this with the 400s below, which are malformed requests and are not billed.

Coverage - what “no interaction” does not cover

coverage is carried in every response and is where unevaluated items land - never a fake all-clear.

FieldTypeDescription
completebooltrue only when every item was resolved and every inter-item pair compared
unresolved_itemsobject[]The input keys you sent that resolved to nothing: { item, input, reason }, reason one of unknown_cis / unknown_ndc / unknown_dci / unknown_class - one entry per key type, so a single list answers “which of my items were not evaluated?”
unevaluated_substancesobject[]Substances of a resolved item that are not comparable to the thesaurus (typically a US ingredient outside the DCI↔INN table and absent from the thesaurus): { item, label }; omitted when empty
notesstringThe scope caveat quoted above

The two lists answer two different questions and are both binding: an identifier you provided that resolved to nothing is an unresolved_items entry (with its typed reason), while a substance found inside a resolved medication but outside the thesaurus is an unevaluated_substances entry (the common case: one substance of a six-substance product is unknown to the ANSM). complete is false as soon as either list is non-empty - and complete: false must never be read as “no interaction”.

{"items":[{"cis":"99999999"},{"dci":"simvastatine"}]} (an unknown but well-formed CIS) returns a 200, billed, with complete: false:

{
  "data": {
    "interactions": [],
    "evaluated_pairs": 0,
    "coverage": {
      "complete": false,
      "unresolved_items": [ { "item": 0, "input": "99999999", "reason": "unknown_cis" } ],
      "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."
  }
}

Item 0 could not be resolved, so no pair was evaluated (evaluated_pairs: 0) and the coverage is honest about it - never a silent interactions: [] read as “safe”.

The same holds for the three other key types: {"dci":"zzzfoo"} comes back as { "item": 0, "input": "zzzfoo", "reason": "unknown_dci" } in the very same list, an unknown class as unknown_class, an unknown ndc as unknown_ndc. Whatever key you send, an identifier that resolved to nothing is in unresolved_items - you never have to read two lists to find out which of your items went unevaluated.

Couples the ANSM splits by class member - applies_to

For 3.6 % of the couples (124 lines out of 3 367), the ANSM publishes the same couple of classes on several lines with different levels, each one restricted to a precise member of the class. Colchicine × strong CYP3A4 inhibitors is the textbook case: a contraindication with the macrolides, and an association advised against with the azole antifungals, the protease inhibitors boosted by ritonavir and cobicistat.

Both lines are returned - all of them always are - and each carries its scope verbatim in applies_to:

{
  "level": "contre_indication",
  "applies_to": "avec les macrolides",
  "management": "- avec les macrolides",
  "mechanism": "Augmentation des effets indésirables de la colchicine, aux conséquences potentiellement fatales."
}

Read applies_to before applying level: clarithromycin is a macrolide, so the contraindication line is the one that applies to it - the second line describes other members of the same class. Whenever at least one returned line is scoped, coverage.notes says so.

The service never drops a line on your behalf. Filtering would mean resolving wording such as “les macrolides” into substance lists, with the risk of discarding a contraindication that did apply - so the scope is exposed, never guessed, and applies_to is the ANSM’s own wording, not a rewrite.

The INN bridge for US medications

A ndc item is resolved to its US product, then to its INN substance, which is the same canonical substance.id used by the whole medication family. That substance is then compared against the French thesaurus. So a US NDC and a French CIS that share a molecule are compared on equal footing. A US ingredient that has no INN bridge and is absent from the thesaurus is reported in coverage.unevaluated_substances[], not silently dropped.

Freshness

Answers are served from a dated snapshot of the ANSM thesaurus; freshness.as_of carries the ANSM publication date of the edition that backed the answer (2024-06-07 for the current edition), not the ingestion date. If a refresh is missed, the service keeps serving the last good snapshot - honestly dated by as_of - and never turns staleness into a 5xx. A thesaurus not yet ingested at all is a 503 DATA_UNAVAILABLE (there is no last good snapshot to serve).

Errors

Only requests the service cannot answer leave the 200 range - none of them are billed.

StatuscodeCase
400INVALID_INPUTFewer than 2 items; more than 50 items; an item with zero or several keys; a malformed CIS/NDC; an empty dci/class; an invalid country; a non-JSON body
503DATA_UNAVAILABLEThe ANSM thesaurus has not been ingested yet - there is no last good snapshot to serve
404NOT_FOUNDUnknown route
500INTERNALInternal error (detail logged, not exposed)
{ "error": "provide at least 2 items", "code": "INVALID_INPUT" }

{ "error": "at most 50 items per call, got 137", "code": "INVALID_INPUT" }

Attribution

Interaction data is derived from the Thésaurus des interactions médicamenteuses — ANSM, the public document published by the ANSM (ansm.sante.fr). Source and publication date are carried in every response. The level, management and mechanism are relayed in the ANSM’s original French text, never reworded or translated.

See also