# Which generics can substitute for this medication? > A BDPM generic group is a regulatory decision, not a computed equivalence. /medication/generics returns every member, its official role and its published price. Published 2026-08-10 · HTML version: https://invoket.com/blog/which-generics-can-substitute-for-this-medication --- An agent handling a prescription, a purchase order or a supplier catalogue eventually asks the sourcing question: *what else can stand in for this product?* [`GET /medication/generics`](/docs/api/medication-generics) answers it from the French **BDPM** generic-groups register — the reference brand (*princeps*), every equivalent, each member's **official BDPM role**, and its boxes with their published price and reimbursement rate. The reason this needs an endpoint rather than a string comparison is the first thing to get straight: a generic group is a **decision published by a regulator**, not a set computed from molecule and dose. Members that a naive match would exclude are in the group, and members a naive match would include are not. ## The group is a register entry, not a computed set In France, substitution is bounded by law. Under [article L. 5125-23 of the code de la santé publique](https://www.legifrance.gouv.fr/codes/article_lc/LEGIARTI000051285174), a pharmacist may dispense a different specialty **within the same generic group**, provided the prescriber has not excluded it with an express, justified mention on the prescription. The groups themselves are established by the ANSM under [article R. 5121-5](https://www.legifrance.gouv.fr/codes/id/LEGISCTA000006190663) and published in the *répertoire des groupes génériques*. Membership of that register is what makes substitution lawful — not chemical similarity. The BDPM publishes the same groups as a data file, and gives each member one of four roles. The endpoint relays that role verbatim as a closed set: | `type` | What the register says | |--------------------------|-----------------------------------------------------------------------| | `princeps` | The reference specialty of the group (letter **R** in the répertoire) | | `generique` | A generic of that reference (letter **G**) | | `complement_posologique` | A generic admitted by *complémentarité posologique* — a different strength | | `substituable` | A substitutable specialty under **R. 5121-5-2** (letter **S**) — a *different* modified-release oral form | Those last two rows are the whole argument for reading the register instead of reimplementing it. ## Two roles a heuristic gets wrong **A different strength, in the group.** Group `33` is labelled `METFORMINE (CHLORHYDRATE DE) 1000 mg - GLUCOPHAGE 1000 mg, comprimé pelliculé sécable.` — a 1000 mg group. It contains two 500 mg members: ```json { "cis": "69448283", "name": "METFORMINE ARROW LAB 500 mg, comprimé pelliculé", "type": "complement_posologique" }, { "cis": "61182628", "name": "METFORMINE CRISTERS PHARMA 500 mg, comprimé pelliculé", "type": "complement_posologique" } ``` Any filter that requires the strengths to match drops them. The register keeps them, and labels *why* they are there. **A different pharmaceutical form, in the group.** Group `698` is labelled `TRAMADOL (CHLORHYDRATE DE) 100 mg - CONTRAMAL L.P. 100 mg, comprimé à libération prolongée - TOPALGIC L.P. 100 mg, comprimé à libération prolongée.` Among its members: ```json { "cis": "69934919", "name": "TRAMADOL ARROW L.P. 100 mg, comprimé à libération prolongée", "type": "substituable" }, { "cis": "69701943", "name": "TRAMADOL ARROW L.P. 100 mg, gélule à libération prolongée", "type": "substituable" } ``` A **capsule** sitting in a group of **tablets**. That is exactly what R. 5121-5-2 provides for: a specialty presented in a modified-release oral form different from the reference can be entered in the group by a decision of the ANSM director general, and is then substitutable like any other member. No attribute of the product tells you this. Only the register does. The same group also shows that a group can carry **more than one** `princeps` — CONTRAMAL LP and TOPALGIC LP are both references of group `698`, and the group label names both. ## Membership, price and availability are three different questions Take the atorvastatin 10 mg group (`913`), reached from the brand: ```http GET /medication/generics?name=TAHOR%2010%20mg%2C%20comprim%C3%A9%20pellicul%C3%A9 ``` It returns **19 members**. Reading them as "19 things I can order" is wrong in three separate ways, and each way is visible in the response. **Prices are per box, and boxes are not the same size.** The princeps sells a **28**-tablet box at `price_cents: 351`; the generics sell **30**-tablet boxes at `price_cents: 318`. Comparing 351 to 318 gives a ~9 % gap; per tablet the gap is closer to 15 %. The endpoint publishes the box `label` next to the price precisely so that the caller does not compare across pack sizes by accident. It does **not** compute a unit price — that is arithmetic on a relayed fact, and it belongs to the caller. **Between generics there is often nothing to compare.** Every 30-tablet generic in this group is priced at exactly `318`, and every 90-tablet one at exactly `904`. Price shopping *inside* a French generic group is largely a non-event; the value of the call is identifying the **legally substitutable set**, not finding a bargain in it. **Three money fields, and never a fourth.** `price_cents` is the medicine, `price_with_dispensing_fee_cents` is what is paid at the counter, and `dispensing_fee_cents` is the pharmacist's fee — three separate BDPM columns, in integer euro cents, never a monetary float and never recomputed from one another. A box whose price the register does not publish carries **none** of the three rather than a guessed `0`. No patient out-of-pocket amount is served: it would depend on coverage the register knows nothing about. **Being in the group does not mean it is on the market.** Presentations carry their BDPM marketing declaration: ```json { "cip13": "3400949458783", "label": "plaquette(s) polyamide aluminium PVC-Aluminium de 30 comprimé(s)", "marketing": "Déclaration d'arrêt de commercialisation", "declared_at": "2022-06-17", "reimbursement_rate": "65%", "price_cents": 373 } ``` In group `913`, three members have **every** box flagged as discontinued — including one of the two `princeps` entries, TAHOR 10 mg *comprimé à croquer*. Add the two members with no identity at all (below) and 5 of the 19 are not something an agent should propose sourcing today. Marketing declarations answer "was this box declared as marketed?"; whether a supply problem has been reported is a different register and a different call — [`GET /medication/availability`](/docs/api/medication-availability). ## A member with no identity says so The BDPM publishes generic groups and specialties in **two separate files**, and the groups file references CIS codes the specialties file no longer carries — authorisations withdrawn and not carried over. That is roughly **24 % of all group members**. Such a member arrives with its `cis`, its role, and nothing else: ```json { "cis": "65785748", "identity": "withdrawn_from_bdpm_specialities", "type": "generique", "is_query": false } ``` No name is guessed from the CIS, and the member is not silently dropped — it belongs to the group, and removing it would misstate the group's composition. The answer also counts the gap at the top level: ```json "coverage": { "members_without_identity": 2, "note": "2 group member(s) have no name or presentations: the BDPM generic-groups file references CIS codes absent from the BDPM specialities file (marketing authorisation withdrawn); nothing is inferred" } ``` A group whose members are all known carries **no** `coverage` at all. This is the same discipline the rest of the family holds: a bound the service knows about is published as a field, not left for the caller to discover in production. ## Three keys, and what `is_query` marks The endpoint takes exactly one of `cis`, `name`, `dci` or `ndc`. Unlike [`/medication/resolve`](/docs/api/medication-resolve), a `name` here takes **no `country`** — generic groups are a French notion, so there is only one register to ask. `dci` goes through the same canonical substance key as the rest of the family, so an English INN reaches the French groups of the same molecule and can aggregate several of them: `dci=acetaminophen` returns **7 groups** and 36 members, combination products included. On every answer, `is_query: true` marks the member(s) that matched the key you sent — one member for a precise `name`, and potentially most of a group for a `dci`, since every member of a paracetamol group contains paracetamol. ## Scope, stated rather than implied Generic groups are a **French (BDPM)** construct, and the endpoint does not pretend otherwise. A US `ndc` key returns a **billed 200** with an honest note instead of an error: ```json { "data": { "found": false, "groups": [], "coverage": { "scope": "FR", "note": "generic groups are a French (BDPM) notion; NDC keys are out of scope in v1" } }, "provenance": { "source": "medication-grid generics coverage (BDPM scope)", "freshness": { "kind": "live" } } } ``` That branch consults no store, so it can never return a `503`. Two further bounds are worth stating plainly: - **Hybrid groups are a different register.** L. 5125-23 also allows substitution within a *groupe hybride*, but the ANSM publishes those in a [separate registre des groupes hybrides](https://ansm.sante.fr/documents/reference/registre-des-groupes-hybrides) (letters R/H). This endpoint serves the BDPM generic-groups file and its four roles — a hybrid relationship is not among them. - **The response carries no excipients with notable effect.** So it cannot tell you whether the `CIF` exclusion of the [arrêté of 12 November 2019](https://www.legifrance.gouv.fr/loda/id/JORFTEXT000039393124/) — one of the three medical situations (`MTE`, `EFG`, `CIF`) in which a prescriber may exclude substitution — applies to a given patient. Which is the general line: the endpoint relays **sourced, dated facts** — roles, names, prices, rates as published. It is **never a substitution recommendation, clinical advice or a patient dosage**. That boundary bounds the whole medication family. ## What you pay for 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 `type`, `marketing` and `coverage` together**, never the member list alone. 4. **Branch** — propose the group, check a candidate's availability, or escalate to a human. A well-formed key carrying no generic group is a **billed 200** with `found: false` and `groups: []` — the agent asked "is this medication part of a generic group?", and "no" is the answer it paid for. Only requests the service cannot answer leave the 200 range, and none of them are billed: ```json { "error": "provide exactly one of: cis, name, dci, ndc", "code": "INVALID_INPUT" } ``` Zero or several keys, a CIS that is not eight digits (`invalid cis: expected exactly 8 digits`), a package-level NDC, an empty `name`/`dci` — all `400`, none billed. Answers come from a **monthly snapshot** of the BDPM, served with no network call and no secret at request time; `freshness.as_of` names the image the answer was built against. A missed refresh keeps serving the last good snapshot, honestly dated — **staleness is never turned into a `5xx`**. [Quickstart](/docs/quickstart) walks the full discover → `402` → pay → replay cycle; price and accepted rails are served live by the [catalog](https://api.invoket.com/catalog), never pinned in an article. ## Chain it `generics` is a middle step. [`GET /medication/resolve`](/docs/api/medication-resolve) turns the CIS or NDC you hold into an identity card — status, canonical substances, presentations — and its `generic_groups` field gives the group `{ id, label }` this endpoint then expands into members. [`GET /medication/availability`](/docs/api/medication-availability) says whether the ANSM has published a shortage report for a candidate before it is proposed, and [`POST /medication/interactions`](/docs/api/medication-interactions) checks that candidate against the rest of a patient's list using the ANSM thesaurus. For identity, generic group, reported shortages and interactions on one medication in a **single settlement**, use the composite [`GET /medication/report`](/docs/api/medication-report). For the full field reference, the `coverage` semantics and the error codes, see the [`GET /medication/generics` documentation](/docs/api/medication-generics); for how agents discover and call Invoket endpoints, see [For agents](/docs/for-agents). BDPM data is reused under [article L. 161-40-1 of the French Social Security Code](https://base-donnees-publique.medicaments.gouv.fr/telechargement), which requires citing the source and its update date — carried in `provenance` on every answer.