Python client for the French INPI « API PI » — the official diffusion API of the Institut national de la propriété industrielle serving the authoritative registries behind data.inpi.fr:
- marques — French trademarks since 1976, EU trademarks (EUIPO) since 1996, international marks (WIPO) since 1891
- brevets — French patents since 1902, EP and WO applications, French CCP/SPCs
- dessins & modèles — French designs since 1910, international designs since 1979
⚠️ This targets the French INPI. Brazil's patent office is also called INPI; this library does not cover it.
INPI publishes an official API, but integrating it is unusually hostile territory:
- The documentation is a PDF of raw curl commands (v1.0, 2021) — there is no OpenAPI spec public without an account, and no official SDK in any language.
- Authentication is a three-step, cookie-based flow: fetch an XSRF token, log in with it, then replay three cookies on every call — one of which must be renamed (
refresh_token→session_token) or requests fail. - The search DSL uses French boolean operators (
ET,OU,SAUF) with bracketed criterion/term couples, and subtle index semantics (Mark_Exp= exact expression vsNGram_Mark= contains). - Failure modes are easy to swallow: expired sessions come back as bare 401s and quota exhaustion returns a 429 that intermediate layers routinely turn into silently-empty results.
Every French legal-tech and IP-analytics team re-solves these problems privately. The
established open source IP tooling — python-epo-ops-client
for the EPO, patent_client for USPTO/EPO —
does not cover INPI. pyinpi fills that gap with a typed, tested, documented client.
pip install git+https://github.com/mounimb/pyinpi(First PyPI release is planned; until then install from git.)
- Create an account on data.inpi.fr.
- In Mon espace client → Mes accès APIs / FTP, request Accès APIs PI for the registries you need (marques / brevets / dessins & modèles).
- INPI emails you an activation link for a technical account; set its password on api-gateway.inpi.fr.
- Use that technical account's email/password with
INPIClient— not your data.inpi.fr web login.
from pyinpi import INPIClient, query
with INPIClient("tech-account@example.org", "password") as inpi:
# Trademarks containing "chidat", FR + EU + WO registries
results = inpi.marques.search(query.mark("chidat"))
# French marks in force, filed by a given applicant
results = inpi.marques.search(
query.et(query.any_of(["DEPOSANT", "DEPOTIT"], "INPI"), query.in_force()),
collections=["FR"],
size=100,
)
# Full XML notice, logo, and BOPI PDF of one mark
notice_xml = inpi.marques.notice("FR4216963")
logo = inpi.marques.image("FR4216963")
bopi_pdf = inpi.marques.bopi("FR4221057")
# Patents: title/abstract keyword search, then one notice
hits = inpi.brevets.search("[(TIT OU ABFR)=(plasma* ET fusion*)]", collections=["FR", "EP"])
notice_xml = inpi.brevets.notice("EP3813503")
# Designs
hits = inpi.modeles.search("[DesignTitle=fauteuil* roulant*]")Raw DSL strings and the pyinpi.query builders are interchangeable everywhere.
Each criterion/term couple is bracketed; operators are French; ? and * are wildcards:
| You want | DSL |
|---|---|
| Deposant named INPI | [DEPOSANT=INPI] |
| Match several indexes | [(DEPOSANT OU DEPOTIT)=INPI] |
| Terms combined | [TIT=(taille* ET haie*)] |
| Couples combined | [DEPOSANT=INPI] OU [DESI=180080012] |
| Exact trademark name | [Mark_Exp=maison des innovateurs] |
| Trademark name contains | [NGram_Mark=chidat] |
| In force today | [ExpiryDate=20260723:99991231] |
Useful marques indexes: ApplicationNumber, Mark_Exp, NGram_Mark, ClassNumber
(Nice classes 1–45), ApplicantIdentifier (SIREN, FR only), DEPOSANT, DEPOTIT,
Representative_LastName, ApplicationDate, ExpiryDate.
Useful brevets indexes: TIT, ABFR, DEPN, PUBN, IPCR, CPC, DENM, TINM,
INVNM, PUBD, DEPD, DLVD, PRD.
See the official PDF for the full index tables.
| Registry | collections |
size max |
position max |
|---|---|---|---|
marques |
FR, EU, WO | 200 | 500 |
brevets |
FR, EP, WO, CCP | 500 | 500 |
modeles |
FR, WO | 100 | 200 |
Note the consequence: you cannot page arbitrarily deep into a result set — narrow your query (dates, classes, collections) rather than crawling.
from pyinpi import AuthenticationError, QuotaExhaustedError
try:
results = inpi.marques.search(query.mark("chidat"))
except QuotaExhaustedError:
... # HTTP 429: account quota used up — back off; do NOT retry in a loop
except AuthenticationError:
... # wrong credentials (technical account!) — the client already retried onceQuotaExhaustedError exists because the single most common INPI integration bug is a
swallowed 429 that surfaces as "French trademarks silently disappeared from results".
Expired sessions (401) are re-authenticated transparently, once per call.
If you proxy requests for many end users, pass forwarded_for= — INPI uses the
x-forwarded-for header for per-user quota accounting.
v0.1 returns raw JSON (search, lists) and XML/bytes (notices, images, PDFs) — the response envelopes are undocumented, so this version does not guess at typed models.
Planned: typed response models · pagination iterator · async client · CLI
(pyinpi marques search ...) · original patent documents endpoint · PyPI release.
Contributions welcome — see CONTRIBUTING.md.
- python-epo-ops-client — EPO Open Patent Services (the European layer; complements this library)
- patent_client — USPTO / EPO / WIPO with an ORM-style API
The data served by the API PI is subject to INPI's own reuse licences (see data.inpi.fr). This library is MIT; the data is not.