GET /medication/resolve

Resolves a medication identifier into its official identity card. On the French side, from the BDPM register (ANSM/HAS): marketing-authorization status, active substances normalized to the INN, presentations with their public price and reimbursement rate, and generic group. The same endpoint resolves a US medication by NDC or name from openFDA + RxNorm. The answer is served from local stores built from those official datasets, so a lookup resolves in milliseconds with no network call and no secret at request time.

Use it when an agent needs a grounded, sourced answer before acting on a prescription or an order - “what is behind this CIS code on a prescription, and is its authorization still active?”, “what is the price and reimbursement rate of this box?”, “what active substance does this US NDC contain?”. The medical vertical on x402 is otherwise a crowd of near-identical openFDA wrappers with no French or EU coverage at all; here it is one paid call, no account, over the official French register. Lookup is by identifier or exact name only - there is no search by symptom, indication or fuzzy name in v1; the endpoint serves agents that already hold an identifier. See the live /catalog for the authoritative endpoint listing and price.

This endpoint returns sourced, dated facts - authorization labels, prices and rates exactly as published. It is never clinical advice, a patient dosage, or a risk score. That line is the boundary of the service.

x402 golden rule: the agent pays for the answer to its question. A well-formed identifier that the register does not carry is a successful answer too -> 200 billed with data.found: false - the absence from the register is the information. Only requests the service cannot answer - no key, several keys, a malformed key, a name without a country - leave the 200 range and are not billed.

Parameters

Exactly one of the four keys, as a query parameter. Zero or several keys, a malformed key, or country without name is a 400 INVALID_INPUT.

ParameterTypeRequiredDescription
cisstringone of the fourFrench CIS code - exactly 8 digits (BDPM)
ndcstringone of the fourUS product NDC labeler-product (e.g. 0071-0155); a package NDC (3 segments) is malformed
namestringone of the fourExact product name - requires country=FR|US
dcistringone of the fourINN/DCI substance name - pure local computation, no store
countrystringwith name onlyFR or US - selects the single register the name is resolved against
GET /medication/resolve?cis=60424338

A name without a country is a 400 MISSING_PARAMETER: a bare name would resolve across both registers and break the single-source provenance, so the caller must say which register to query. Name resolution folds case, accents and punctuation and may return several medications.

200 response - UnifiedResponse

{
  "data": { ... },
  "provenance": {
    "source": "BDPM",
    "fetched_at": "2026-07-19T18:10:33Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-18T00:00:00Z" }
  }
}

provenance depends on the key that resolved the answer:

Key(s)sourcefreshness.kind
cis, name + country=FRBDPMsnapshot - monthly BDPM image
ndc, name + country=USopenFDA + RxNormsnapshot - openFDA dump date
dcimedication-grid DCI/INN substance tablelive - pure local computation

Fields of data

FieldTypeDescription
foundboolfalse alone when the key is well-formed but unknown to the register - a billed 200
medicationsobject[]Present for cis / ndc / name - one entry per matched medication (see below)
substanceobjectPresent for dci only - the canonical substance, present only when the DCI mapped to the INN table
coverageobjectPresent only when some substance label could not be normalized (see below)

A field the source does not carry is omitted from the JSON, never served null: a US result carries no status or presentations, a French result carries no generic_name.

A medications[] entry

FieldTypeDescription
countrystringFR or US
identifiersobject{ cis } (FR) or { ndc } (US)
namestringProduct name (brand)
generic_namestringUS only - openFDA generic name when distinct from the brand name
formstringPharmaceutical form
routesstring[]Routes of administration
holderstringMarketing-authorization holder (FR) / labeler (US)
statusstringFR only - marketing-authorization status, as published
marketing_statusstringFR only - marketing status
authorization_datestringFR only - authorization date (YYYY-MM-DD)
enhanced_surveillanceboolFR only - ANSM black-triangle enhanced-surveillance flag
dispensingstring[]Dispensing conditions as published (FR); prescription / otc derived from product_type (US)
substancesobject[]Active substances normalized to the INN - the FR↔US bridge (see below)
presentationsobject[]FR only - boxes with price and reimbursement rate (see below)
generic_groupsobject[]FR only - { id, label } of the generic group only - the members belong to a dedicated /medication/generics endpoint (not documented here)

A presentations[] entry (FR only)

FieldTypeDescription
cip7 / cip13stringCIP presentation codes
labelstringPackaging label
price_centsintPublic price in euro cents (integer, never a monetary float); omitted when the price is free/unrecorded
reimbursement_ratestringUNCAM rate as published (e.g. "65%"); omitted when not reimbursed/unrecorded
collectivities_approvedboolApproved for use by public bodies

Prices and rates are relayed exactly as published - they are facts, not a recommendation. The generic-group label is the group name only; its members are the job of a dedicated /medication/generics endpoint.

The FR↔US bridge is the substance

Every active substance in a medications[] entry is normalized to its INN, and a French cis and a US ndc for the same molecule carry the same substances[].id. That normalized id is the join key: it is what will let a US medication be looked up against the French ANSM interaction thesaurus. The paracetamol of a French DOLIPRANE and a US DOLOCOMBO both resolve to id: "paracetamol".

Coverage honesty

Some published substance labels do not match an entry in the DCI↔INN table. The service never guesses them: it relays the published label as-is in substances[] with mapped: false, and lists it in coverage.unmapped_substances. When every substance normalized, coverage is absent. The service relays the source’s label; it does not invent one.

Example - French medication, resolved by CIS

GET /medication/resolve?cis=60424338 (AMOXICILLINE VIATRIS), against the 2026-07-18 BDPM snapshot:

{
  "data": {
    "found": true,
    "medications": [
      {
        "country": "FR",
        "identifiers": { "cis": "60424338" },
        "name": "AMOXICILLINE VIATRIS 500 mg, gélule",
        "form": "gélule",
        "routes": ["orale"],
        "holder": "VIATRIS SANTE",
        "status": "Autorisation active",
        "marketing_status": "Commercialisée",
        "authorization_date": "1998-12-10",
        "enhanced_surveillance": false,
        "substances": [
          {
            "id": "amoxicilline trihydratee",
            "labels_fr": [],
            "labels_en": [],
            "raw_label": "AMOXICILLINE TRIHYDRATÉE",
            "source_code": "28165",
            "mapped": false
          }
        ],
        "presentations": [
          {
            "cip7": "3491317",
            "cip13": "3400934913174",
            "label": "plaquette(s) thermoformée(s) PVC-aluminium PVDC de 12 gélule(s)",
            "marketing": "Déclaration de commercialisation",
            "declared_at": "2014-07-04",
            "collectivities_approved": true,
            "reimbursement_rate": "65%",
            "price_cents": 153
          }
        ],
        "generic_groups": [
          { "id": 148, "label": "AMOXICILLINE 500 mg - CLAMOXYL 500 mg, gélule" }
        ]
      }
    ],
    "coverage": {
      "unmapped_substances": ["AMOXICILLINE TRIHYDRATÉE"]
    }
  },
  "provenance": {
    "source": "BDPM",
    "fetched_at": "2026-07-19T18:10:33Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-07-18T00:00:00Z" }
  }
}

Example - US medication, resolved by NDC

GET /medication/resolve?ndc=0071-0155 resolves against the openFDA/RxNorm store. The entry carries country: "US", identifiers.ndc, name (brand) and generic_name, dispensing: ["prescription"], and substances[] whose id values are the same as on the French side - the bridge. It carries no status, presentations or generic_groups (French only), and provenance.source is "openFDA + RxNorm". Note there is no US price: the price and reimbursement fields are French only.

Example - substance, resolved by DCI

GET /medication/resolve?dci=paracetamol returns data.substance (the canonical substance) with no medications, provenance.source: "medication-grid DCI/INN substance table" and freshness.kind: "live" - it is a pure local computation over the committed DCI↔INN table, consulting no store (so a dci key never returns a 503). A DCI outside the table is a billed 200 with data.found: false, never a guess.

A well-formed but unknown key is a paid answer

GET /medication/resolve?cis=99999999 - a well-formed CIS the register does not carry - returns a 200, billed, with data.found: false alone. The agent asked “is this identifier a real medication?”, and “the register does not carry it” is the answer. Contrast this with the 400s below, which are malformed requests and are not billed.

Freshness

French answers are served from a monthly snapshot of the BDPM register, US answers from the dated openFDA dump; freshness.as_of carries the date of the image that backed the answer. If a monthly refresh is missed, the service keeps serving the last good snapshot - honestly dated by as_of - and never turns staleness into a 5xx. A register not yet ingested at all is a 503 DATA_UNAVAILABLE (there is no last good snapshot to serve); a dci key depends on no store and never returns 503.

Errors

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

StatuscodeCase
400INVALID_INPUTZero or several keys; a malformed key (e.g. a cis that is not 8 digits, a package NDC); country without name; an invalid country
400MISSING_PARAMETERname provided without country
503DATA_UNAVAILABLEThe required register has not been ingested yet - there is no last good snapshot to serve
404NOT_FOUNDUnknown route
500INTERNALInternal error (detail logged, not exposed)
{ "error": "provide exactly one of: cis, ndc, name, dci", "code": "INVALID_INPUT" }
{ "error": "parameter `name` requires `country` (FR or US)", "code": "MISSING_PARAMETER" }

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 /company/resolve - resolve a French company from its SIREN or SIRET before onboarding a counterparty.
  • GET /iban/screen - screen a counterparty’s IBAN against the OFAC/EU/UN sanctions lists before paying it.
  • For agents - discovery surfaces, the live /catalog and how settlement works.