GET /medication/lookup
Turns a medication name as an agent holds it - a brand from a
prescription, a US product name, a French or international substance name -
into the identifiers the /medication/* routes require: a French CIS
(one presentation in the BDPM register), a US product NDC, or the
canonical identifier of an active substance. The answer comes from local
stores already ingested (BDPM, openFDA, RxNorm), with no network call and no
secret at request time.
Start here when the agent holds a name. If it already holds a CIS or an NDC,
call /medication/resolve directly. See the
live /catalog for the authoritative listing and price.
ambiguous is the normal answer here
Read this before the examples: a brand name routinely covers several
strengths, forms and presentations, and each presentation is its own CIS.
“Doliprane” is 18 CIS in the current BDPM stock - tablets, suppositories,
oral powders, from 100 mg to 1000 mg. Returning “the” CIS for Doliprane would
be wrong in most cases, so this route does not: ambiguous is the
nominal case, and the list is the answer.
verdict | Meaning | candidates |
|---|---|---|
exact | One candidate answers | exactly one element |
ambiguous | Several do - all served, up to 50, with total exact. The usual case | two or more elements |
none | The referentials publish nothing for this name, at the stock date | [] |
candidates is always present - empty on none, a one-element list on
exact. Each candidate carries what tells it apart from the others (country,
strength, form, marketing-authorisation holder, French marketing status), so
the caller can choose.
none is a paid answer
A none is served as a 200 and billed - never a 404. “This name is
not in the referential, as of the stock date” is exactly what stops an agent
from inventing a CIS. Only a malformed request - q missing, empty, without
any letter or digit, or a filter outside its domain - leaves the 200 range,
and it is not billed.
What a resolution does not attest
Every answer carries the same fixed sentence in data.limits, served
verbatim:
A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. A brand name routinely covers several strengths, forms and presentations, hence several CIS: choosing between them belongs to the caller, and no candidate served here is medical advice. Substance candidates fold salt and hydrate forms onto their base through a closed curated list (the fold is visible in normalization.substance_key): a form that list does not cover keeps its suffix and yields no substance candidate rather than a guessed one. Matching is exact on the published label or on its leading words, never fuzzy and never scored; candidates are ordered substance first, then by CIS, then by NDC — a reading order, not a relevance ranking.
This route names what three public registers publish. It does not say which presentation suits anyone, and nothing in a candidate is a recommendation - the order is a reading order, and the choice between presentations belongs to the caller.
Three natures of identifier
Each candidate’s id is the key downstream accepts as is, and id_kind
says which kind it is:
id_kind | id | What it designates | Where it goes |
|---|---|---|---|
cis | 8 digits (62080626) | One French presentation in the BDPM | cis= on resolve, generics, availability, report; a cis item of interactions |
ndc | labeler-product (50580-108) | One US product (openFDA / RxNorm) | ndc= on resolve and report; an ndc item of interactions |
substance | INN form (acetylsalicylic acid) | An active substance, whatever the country | dci= on resolve, generics, availability; a dci item of interactions |
The substance is the bridge between France and the US: the same
substance id resolves on both sides, which is how a French CIS and a US NDC
meet in one interaction check. It is also the only nature that is not a
presentation - it carries no strength, no form, no holder. A substance id
goes into the dci key of an interactions item, never into its cis
key; the report route takes a presentation (cis or ndc) as its subject,
not a substance.
A single name often returns a substance and presentations - “aspirine”
returns the substance acetylsalicylic acid and eight French presentations
whose published name starts with “Aspirine”. That is a normal ambiguous,
not an anomaly: the caller decides whether it is asking about the molecule or
about a box.
How the name is matched
Two keys are computed from your text, and both are served back in
data.normalization - without them, a none would be inexplicable:
name_key- matched against published product names (BDPM denominations, US brand and generic names): lower case, French accents folded, all punctuation, hyphens included, turned into a word boundary, spaces collapsed.DOLIPRANE,doliprane,Dolipranêandsachet-dose/sachet doseland on the same key.substance_key- matched against active substances: the same folding, plus the folding of known salt and hydrate forms onto their base (esomeprazole magnesium→esomeprazole,sertraline hydrochloride→sertraline).
Matching is an equality on word boundaries: a published label matches
when it is the key, or starts with the key followed by a word
boundary. So “Tylenol” returns TYLENOL EXTRA STRENGTH - the label that
carries the match is served, the difference is visible, and a partial match
is never exact unless it is the only one. There is no edit distance, no
similarity score, no typo tolerance.
What is deliberately not folded:
| You send | Key | Result |
|---|---|---|
parace-tamol | parace tamol | none - words are never glued back together, because the indexed column is not either |
Efferalgan 1g | efferalgan 1g | none - the BDPM publishes EFFERALGAN 1000 mg, ...: 1g is not a word of any label. Send Efferalgan and filter with strength=1000 mg |
Salt and hydrate forms: a closed list, on purpose
The salt fold runs on a closed, curated list of salt, counter-ion and
hydrate tokens (hydrochloride, chlorhydrate, besilate, sodium,
succinate, dihydrate…). Outside that list, the suffix stays, and
no substance candidate is returned rather than a guessed one:
hydroxyzine pamoate keeps its key hydroxyzine pamoate, and comes back with
the US products whose label carries those words - but no substance
candidate.
That restriction is the guarantee, not a gap. A generic “strip whatever looks like a salt suffix” rule was measured fabricating false interactions on the interactions route: it reduced both potassium chloride and sodium chloride to the same key, and handed a potassium-only product the interaction the ANSM thesaurus publishes for sodium salts. Every token on the list is one that was checked against the referentials; a form it does not cover is served as unresolved rather than mapped to a substance it may not be.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | yes | The name as you hold it - brand, product name, substance. At least one letter or digit, up to 1,024 characters |
country | string | no | FR or US - limits the registers consulted |
form | string | no | A pharmaceutical form as published (comprimé, suppositoire, TABLET) |
strength | string | no | A strength as published (1000 mg, 500 mg/1) |
GET /medication/lookup?q=Doliprane&form=suppositoire
- Filters reduce, they never reshape. Each one keeps the candidates whose
published value contains the filter on word boundaries:
form=comprimékeepscomprimé effervescent(e) sécable,strength=500 mgkeeps500 mg + 30 mg- but never1500 mg. - A substance candidate leaves the set as soon as
formorstrengthis set: a substance publishes neither, so nothing attests that it matches. Likewise a presentation whose register does not publish the filtered field is dropped. The service does not fall back silently on what it cannot attest. countrylimits the registers:country=USdoes not open the BDPM, soDolipranebecomes anone; a substance stays servable underUS, because the substance table is the bridge and belongs to neither country.- No pagination. The list is capped at 50 (the item cap of
/medication/interactions);totalcounts every candidate before the cap andtruncatedsays whether the list was cut - “paracetamol” finds 116 candidates. A second page would be a second paid call for the same question: narrow with a filter instead. A truncated list is neverexact.
Every filter applied is echoed in data.filters.
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "BDPM + openFDA + RxNorm",
"fetched_at": "2026-10-15T15:37:33.138077267Z",
"freshness": { "kind": "snapshot", "as_of": "2026-10-08T00:00:00Z" }
}
}
provenance.source: the registers actually consulted, joined by+.freshness.as_of: the oldest snapshot among them - an answer is never dated fresher than its oldest data.
Fields of data
| Field | Type | Description |
|---|---|---|
query | string | The text you sent, echoed |
verdict | string | exact, ambiguous or none |
candidates | object[] | Always served. Substance first, then French presentations by CIS, then US products by NDC - a reading order, not a relevance ranking. At most 50 |
total | number | Candidates found before the cap; the verdict is derived from it |
truncated | bool | Whether the served list is cut |
normalization | object | { name_key, substance_key } - the two keys actually searched |
filters | object | The filters applied, echoed ({} when none) |
limits | string | The fixed sentence quoted above |
Each candidate:
| Field | Type | Description |
|---|---|---|
id | string | CIS, NDC or substance identifier - pass it downstream as is |
id_kind | string | cis, ndc or substance |
label | string | The name as the register publishes it - never recased, never re-accented |
source | string | The register the candidate comes from (BDPM, openFDA + RxNorm, or the DCI/INN substance table) |
country | string | FR or US - presentations only |
strength | string | Published strength(s) of the active substance(s), joined by + for a combination |
form | string | Published pharmaceutical form |
holder | string | Marketing-authorisation holder (FR) or labeler (US) |
marketing_status | string | French marketing status as published (Commercialisée, Non commercialisée) |
A field the register does not publish is absent, never null and never
filled in.
Example - exact
GET /medication/lookup?q=Levothyrox%2075:
{
"data": {
"query": "Levothyrox 75",
"verdict": "exact",
"candidates": [
{
"id": "62080626",
"id_kind": "cis",
"label": "LEVOTHYROX 75 microgrammes, comprimé sécable",
"country": "FR",
"strength": "75 microgrammes",
"form": "comprimé sécable",
"holder": "MERCK SANTE",
"marketing_status": "Commercialisée",
"source": "BDPM"
}
],
"total": 1,
"truncated": false,
"normalization": { "name_key": "levothyrox 75", "substance_key": "levothyrox 75" },
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. A brand name routinely covers several strengths, forms and presentations, hence several CIS: choosing between them belongs to the caller, and no candidate served here is medical advice. Substance candidates fold salt and hydrate forms onto their base through a closed curated list (the fold is visible in normalization.substance_key): a form that list does not cover keeps its suffix and yields no substance candidate rather than a guessed one. Matching is exact on the published label or on its leading words, never fuzzy and never scored; candidates are ordered substance first, then by CIS, then by NDC — a reading order, not a relevance ranking."
},
"provenance": {
"source": "BDPM + openFDA + RxNorm",
"fetched_at": "2026-10-15T15:37:33.138077267Z",
"freshness": { "kind": "snapshot", "as_of": "2026-10-08T00:00:00Z" }
}
}
“Levothyrox” alone is 13 presentations; with the strength in the text, one
published label starts with those words. The id goes straight into
/medication/resolve?cis=62080626.
Example - ambiguous, a substance and its presentations
GET /medication/lookup?q=aspirine (limits and provenance as above):
{
"data": {
"query": "aspirine",
"verdict": "ambiguous",
"candidates": [
{ "id": "acetylsalicylic acid", "id_kind": "substance", "label": "ACIDE ACÉTYLSALICYLIQUE", "source": "BDPM" },
{ "id": "61172160", "id_kind": "cis", "label": "ASPIRINE UPSA TAMPONNEE EFFERVESCENTE 1000 mg, comprimé effervescent", "country": "FR", "strength": "1000 mg", "form": "comprimé effervescent(e)", "holder": "UPSA", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "61464022", "id_kind": "cis", "label": "ASPIRINE PROTECT 100 mg, comprimé gastro-résistant", "country": "FR", "strength": "100 mg", "form": "comprimé gastro-résistant(e)", "holder": "BAYER HEALTHCARE", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "61946403", "id_kind": "cis", "label": "ASPIRINE ARROW 100 mg, comprimé gastro-résistant", "country": "FR", "strength": "100 mg", "form": "comprimé gastro-résistant(e)", "holder": "ARROW GENERIQUES", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "65265765", "id_kind": "cis", "label": "ASPIRINE UPSA 500 mg, comprimé effervescent", "country": "FR", "strength": "500 mg", "form": "comprimé effervescent(e)", "holder": "UPSA", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "66598317", "id_kind": "cis", "label": "ASPIRINE ARROW 75 mg, comprimé gastro-résistant", "country": "FR", "strength": "75 mg", "form": "comprimé gastro-résistant(e)", "holder": "ARROW GENERIQUES", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "67831952", "id_kind": "cis", "label": "ASPIRINE UPSA VITAMINEE C TAMPONNEE EFFERVESCENTE, comprimé effervescent", "country": "FR", "strength": "0,330 g + 0,200 g", "form": "comprimé effervescent(e)", "holder": "UPSA", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "68799614", "id_kind": "cis", "label": "ASPIRINE DU RHONE 500 mg, comprimé", "country": "FR", "strength": "500 mg", "form": "comprimé", "holder": "BAYER HEALTHCARE", "marketing_status": "Commercialisée", "source": "BDPM" },
{ "id": "69029253", "id_kind": "cis", "label": "ASPIRINE DU RHONE 500 mg, comprimé à croquer", "country": "FR", "strength": "500 mg", "form": "comprimé à croquer", "holder": "BAYER HEALTHCARE", "marketing_status": "Commercialisée", "source": "BDPM" }
],
"total": 9,
"truncated": false,
"normalization": { "name_key": "aspirine", "substance_key": "aspirine" },
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, …"
},
"provenance": { "…": "…" }
}
The substance comes first and carries the INN as its id
(acetylsalicylic acid) next to the label the BDPM publishes
(ACIDE ACÉTYLSALICYLIQUE): that id is what a dci item of
/medication/interactions accepts as is. The eight presentations are the
French products whose published name starts with “Aspirine”; telling them
apart is what strength, form and holder are for.
Example - none
GET /medication/lookup?q=Efferalgan%201g:
{
"data": {
"query": "Efferalgan 1g",
"verdict": "none",
"candidates": [],
"total": 0,
"truncated": false,
"normalization": { "name_key": "efferalgan 1g", "substance_key": "efferalgan 1g" },
"filters": {},
"limits": "A resolution names what the referential publishes for the text you sent, as of the date carried by the provenance; it does not certify that the entity resolved is the one you had in mind. A brand name routinely covers several strengths, forms and presentations, hence several CIS: choosing between them belongs to the caller, and no candidate served here is medical advice. Substance candidates fold salt and hydrate forms onto their base through a closed curated list (the fold is visible in normalization.substance_key): a form that list does not cover keeps its suffix and yields no substance candidate rather than a guessed one. Matching is exact on the published label or on its leading words, never fuzzy and never scored; candidates are ordered substance first, then by CIS, then by NDC — a reading order, not a relevance ranking."
},
"provenance": {
"source": "BDPM + openFDA + RxNorm",
"fetched_at": "2026-10-15T15:37:33.227079665Z",
"freshness": { "kind": "snapshot", "as_of": "2026-10-08T00:00:00Z" }
}
}
A 200, billed. No published label starts with the words efferalgan 1g -
the BDPM writes the strength 1000 mg. The normalization echo shows exactly
what was searched, which is what makes the none explainable:
q=Efferalgan&strength=1000 mg is the call that finds the presentations.
Errors
Only requests the service cannot answer leave the 200 range - none of them
are billed. There is no 404 for a name the registers do not publish:
that is a none.
| Status | code | Case |
|---|---|---|
| 400 | MISSING_PARAMETER | q absent, empty or blank |
| 400 | INVALID_INPUT | q without any letter or digit, or longer than 1,024 characters; an empty form/strength; a country other than FR/US; a repeated parameter |
| 405 | METHOD_NOT_ALLOWED | Any method other than GET |
| 503 | DATA_UNAVAILABLE | A register the request needs has not been ingested yet - rather than a none that would pass a coverage gap off as an absence |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "code": "MISSING_PARAMETER", "error": "missing or empty required parameter: q" }
{ "code": "INVALID_INPUT", "error": "invalid country: expected FR or US, got \"DE\"" }
Attribution
French medication data is derived from 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 (source and date required). US medication data is derived from openFDA and RxNorm, in the public domain (NLM/FDA).
See also
GET /medication/resolve- the full record behind a CIS, an NDC or a substance.POST /medication/interactions- the ANSM interaction thesaurus across 2 to 50 items; asubstanceid goes into adciitem.GET /medication/report- one presentation, every check in one call.GET /company/lookup,GET /vehicle/lookupandGET /label/lookup- the samelookupcontract for French companies, vehicles and EU labelling ingredients.- For agents - discovery surfaces, the live
/catalogand how settlement works.