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"
}
| Field | Type | Required | Description |
|---|---|---|---|
items | object[] | yes | 2 to 50 items, each with exactly one of the keys below; beyond 50 the 400 states the limit |
country | string | no | FR 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:
| Key | Type | Description |
|---|---|---|
cis | string | French CIS code - exactly 8 digits (BDPM) |
ndc | string | US product NDC labeler-product (e.g. 0071-0155) - joins the thesaurus through the INN bridge |
dci | string | INN/DCI substance name, resolved through the shared canonical substance key (non-empty) |
class | string | An 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.
level | level_label | Meaning (ANSM) |
|---|---|---|
contre_indication | contraindication | The most severe - the association must not be used |
association_deconseillee | advised against | The association is advised against |
precaution_emploi | precaution for use | The association is possible with a precaution for use |
a_prendre_en_compte | to be taken into account | The 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
| Field | Type | Description |
|---|---|---|
interactions | object[] | The interactions found between inter-item pairs, sorted by decreasing severity; [] when none |
evaluated_pairs | integer | The number of inter-item pairs actually compared - the scope over which “no interaction” is asserted |
coverage | object | What was and was not evaluated (see below) - carried in every response |
disclaimer | string | The 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
| Field | Type | Description |
|---|---|---|
a, b | object | The two protagonists (see below) |
level | string | The severity code - one of contre_indication / association_deconseillee / precaution_emploi / a_prendre_en_compte |
level_label | string | The English label of the level |
management | string | The management to follow, ANSM verbatim in French; omitted when the ANSM published none |
applies_to | string | The 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 |
mechanism | string | The mechanism, ANSM verbatim in French; omitted when the ANSM published none |
source | string | "Thésaurus ANSM" |
Each protagonist (a / b) carries:
| Field | Type | Description |
|---|---|---|
item | integer | The 0-based index of the item in the list you sent |
matched | string | The canonical substance or class actually compared |
ansm_protagonist | string | The 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.
| Field | Type | Description |
|---|---|---|
complete | bool | true only when every item was resolved and every inter-item pair compared |
unresolved_items | object[] | 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_substances | object[] | 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 |
notes | string | The 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.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Fewer 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 |
| 503 | DATA_UNAVAILABLE | The ANSM thesaurus has not been ingested yet - there is no last good snapshot to serve |
| 404 | NOT_FOUND | Unknown route |
| 500 | INTERNAL | Internal 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
GET /medication/availability- whether a medication in the list is short before acting on the interaction check.GET /medication/generics- the equivalents of a medication flagged by an interaction, to source an alternative.GET /medication/resolve- the identity card and canonical substances behind each item you send here.- For agents - discovery surfaces, the live
/catalogand how settlement works.