EUIPO API in 2026: Access, OAuth, RSQL Search, and What It Can't Query

Yes, there is an EUIPO API: free REST APIs on dev.euipo.europa.eu. How to get access, authenticate with OAuth, search trademarks in RSQL, and where it stops.
15 min read

Yes, EUIPO has an official API: the EUIPO API Portal offers free REST APIs, Trademark search included. Access is gated: you need an EUIPO user account, a registered app, approved subscriptions and, for production, identity documents. Trademark search covers EUIPO's own register only, queries are written in an exact-match and wildcard filter language, and the spec lists no fuzzy or phonetic operator.

Everything below is as of November 5, 2026, with a source link on each claim. This guide is for developers who need EU trade mark data inside their own code. To search by hand, read the European trademark search guide; for the US equivalent of this article, see the USPTO trademark API guide.

Does EUIPO Have an API?

The portal's pitch is "Connect your firm with the EUIPO", in three steps: Explore, Register build & test (in a sandbox), and Apply (for production). Its API Products catalogue lists 11 REST APIs, and the FAQ is unambiguous on cost: "Access to Production and Sandbox APIs is free of charge."

Six of the 11 matter for trade marks. An EUTM (EU trade mark) is a single registration covering the whole European Union, filed with EUIPO.

API (version)What it does
Trademark search 1.1.0Search EUIPO's trade mark register and read records, images and other media
Goods And Services 1.2.0Search, validate and translate goods and services terms (the TMClass taxonomy)
EUTM Filing 1.1.0File EU trade mark applications
Me 1.2.0Manage your own portfolio
My EUIPO mailbox 1.2.1Read the User Area inbox and alerts
Persons 1.0.0Search persons in EUIPO procedures

The other five cover designs (Design search, EUD Filing, Product Indications), the Document Repository and Out of Commerce Works.

Getting EUIPO API Access: Account, Sandbox, Production

The path, from the getting started page and the FAQ:

  1. Create an EUIPO user account.
  2. Register an app. Sign in to the API Portal and add an app under Apps. You get a Client ID and a Client Secret. The secret is shown only once, though you can reset it.
  3. Subscribe the app to an API plan and accept the conditions of use. "Most subscription requests are subject to approval." The general policy is one subscription to the same API per organisation.
  4. Pass integration tests in the sandbox, then apply for a production subscription.

The two EUIPO pages give different timings. Getting started says a sandbox subscription "can take up to a week" and production "may take a bit longer". The FAQ says "The average response time for a subscription request is one day for Sandbox and up to one week for Production."

Production also needs identity documents. A natural person sends a copy of a national ID card or passport plus proof of address. A legal person sends the acting person's ID plus a company register excerpt showing that person may act for the entity. Documents go to docs.apiplatform@euipo.europa.eu, and a request is assessed only once they arrive.

Sandbox and production are separate systems. They use different auth servers (a token from one returns 401 on the other), they hold different data, and the FAQ is explicit: "There is no aim to align Sandbox and Production." The sandbox portal lives at dev-sandbox.euipo.europa.eu. Tests that pass against sandbox data may return different results in production.

Authentication: OpenID Connect over OAuth 2.0

EUIPO's security page describes OpenID Connect on top of OAuth 2.0, with two flows:

  • Client credentials identifies only your app. Trademark search uses this flow with scope uid.
  • Authorization code (accessCode in the spec) identifies the end user as well. EUTM Filing uses it, with scopes such as eutm-filing.application.write.

The flow affects what you get back. The Trademark search OpenAPI spec says that with client credentials "the final user remains anonymous" and "under certain conditions, the API may choose to hide specific parts of the trade mark information", while the authorization code flow "can provide complete information".

Every call carries two headers: the app's Client ID in X-IBM-Client-Id (an apiKey scheme in the spec) and Authorization: Bearer <access_token>. Per the FAQ, an access token lives 2 hours and a refresh token 4 hours. The production token URL comes from the spec; the sandbox one from the security page.

Signa has no EUIPO subscription, so the authenticated request was not run for this article. These commands follow EUIPO's security page and OpenAPI spec:

# 1. Token (client credentials). Production token URL from the OpenAPI spec.
# Sandbox: https://auth-sandbox.euipo.europa.eu/oidc/accessToken (tokens don't cross environments)
curl -s -X POST "https://euipo.europa.eu/cas-server-webapp/oidc/accessToken" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "client_id=$EUIPO_CLIENT_ID" \
  --data-urlencode "client_secret=$EUIPO_CLIENT_SECRET" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "scope=uid"

# 2. Search (production server from the OpenAPI spec)
curl -sG "https://api.euipo.europa.eu/trademark-search/trademarks" \
  -H "X-IBM-Client-Id: $EUIPO_CLIENT_ID" \
  -H "Authorization: Bearer $EUIPO_ACCESS_TOKEN" \
  --data-urlencode "query=wordMarkSpecification.verbalElement==OATLY and status==REGISTERED" \
  --data-urlencode "size=10" \
  --data-urlencode "page=0"

Without credentials, the search request returned HTTP 401 on November 5, 2026:

{"type":"https://dev.euipo.europa.eu/problems-registry/unauthorized","title":"Unauthorized","detail":"Invalid client id or secret.","status":401,"instance":"/trademark-search/trademarks"}

Errors are RFC 7807-style problem JSON, and the type URL points into the portal's Problems registry.

Querying the EUIPO Trademark Search API

Endpoints

All operations sit under https://api.euipo.europa.eu/trademark-search (spec):

  • GET /trademarks searches.
  • GET /trademarks/{applicationNumber} returns one record. It accepts international registration numbers with a trailing letter, such as W00843427A. Under the Madrid Protocol, an owner files one international application and designates the countries or regions it wants covered; the result is an international registration. An Accept-Language header filters goods and services, description, disclaimer and colour claim to one language.
  • GET /trademarks/{applicationNumber}/image, /image/thumbnail, /sound, /video and /model return the mark's media.

Version 1.1.0 added publicationDate and publicationSection as query fields, a sectionA1PublicationDate sort and a publications field on results.

RSQL in the query parameter

Search takes one query parameter written in RSQL, a URL-friendly filter syntax built from field, operator and value. The operators are ==, !=, &lt;, &lt;=, >, >=, =in=, =out= and =all=. Combine conditions with and/or (or ; and ,). An asterisk is a wildcard with == and !=, and dates are yyyy-MM-dd.

Filterable fields include wordMarkSpecification.verbalElement (the mark text), applicants.name, representatives.name, niceClasses, status, markFeature, markKind, markBasis, the date fields (applicationDate, registrationDate, expiryDate, publicationDate, oppositionPeriodEndDate, updateDate), renewalStatus, fastTrackIndicator, and the numbers of related oppositions, cancellations and appeals. This example comes straight from the spec:

niceClasses=all=(25,28,40) and wordMarkSpecification.verbalElement==*Dog* and status==REGISTERED

Fields, sort and paging

fields is a projection grammar, for example trademarks(applicationNumber,applicants(identifier)). sort takes field:asc or field:desc on fields such as applicationDate, registrationDate, updateDate and sectionA1PublicationDate.

Paging uses page and size. page is zero-based and defaults to 0; size runs from 10 to 100 and defaults to 10. The FAQ calls out the trap itself: page 1 is the second page.

Rate limits

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After, and exceeding your plan returns 429. The product page shows one Default Plan with two rate limits on each operation, but the numbers are visible only after sign-in, so none are given here.

The Conditions of Use (Annex I v2 to Decision EX-25-1) let EUIPO "limit the frequency of API transactions" at its sole discretion and change rate limits with notice on the portal. Users "must not attempt to circumvent rate limits, including by registering on the API Portal with different accounts." The Office may also change the Conditions of Use or any API specification without prior notice. Some APIs return personal data of parties and representatives, which is public under Articles 111 and 112 EUTMR, and EUIPO is the controller of that data.

What it doesn't do

  • No similarity matching. The spec lists no fuzzy or phonetic operator. Matching on mark text is equality or asterisk wildcards on verbalElement, so you have to guess the variants yourself.
  • One register. Trademark search covers "EUIPO's database": EUTMs plus international registrations designating the EU. National registers of EU member states are not included; for those, TMview is the place to search.
  • No SDK. "EUIPO API does not provide clients"; the FAQ suggests generating one from the OpenAPI descriptors.
  • No computed deadlines. Records do carry oppositionPeriodEndDate, expiryDate and renewalStatus, but renewal windows and grace periods are your own date arithmetic.

One quirk from the FAQ: a 302 redirect to euipo.europa.eu/error/revise.html is "usually related to a reputation issue with your public IP", and EUIPO points to BrightCloud for checking it. If your server's IP has a poor reputation score, calls can be redirected there instead of reaching the API.

Bulk data: Signa could not verify a current EUIPO bulk-download page on November 5, 2026; the old open-data URL redirects to the EUIPO homepage.

The Same Jobs Through Signa's API

The gaps are approval time, a single register and exact-match queries. Signa indexes 3.27 million EUIPO records, synced daily, in the same schema as its other 12 live offices (Signa data, GET /v1/offices/EM, queried November 5, 2026). How that schema is built is covered in normalizing trademark data across offices.

Keys are self-serve at app.signa.so with no approval step, and belong on the server side only; the quickstart covers setup. Every call below ran against production on November 5, 2026, with output trimmed by jq.

Search the EU register by name

The equivalent of the RSQL verbalElement==OATLY query:

curl -sG "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=oatly" \
  --data-urlencode "offices=EM" \
  --data-urlencode "status_primary=active" \
  --data-urlencode "limit=5" \
  | jq -c '.data[] | {mark_text, tier: .match.tier, number: .application_number, owner: .owners[0].name, classes: .nice_classes}'
{"mark_text":"OATLY","tier":"identical","number":"018479993","owner":"Oatly AB","classes":[31]}
{"mark_text":"OATLY","tier":"identical","number":"019094795","owner":"Oatly AB","classes":[3,11,21,25,29,30,32]}
{"mark_text":"OATLY","tier":"identical","number":"001626233","owner":"Oatly AB","classes":[29,32]}
{"mark_text":"OATLY","tier":"identical","number":"016364441","owner":"Oatly AB","classes":[5,9,14,18,21,25,29,30,32,35,39,41,43]}
{"mark_text":"OATLY","tier":"identical","number":"018004393","owner":"Oatly AB","classes":[3,16]}

Five live EUTMs, all owned by Oatly AB, each with a different set of Nice classes. Nice classes are the international categories for goods and services: class 29 covers foods such as milk substitutes, class 32 non-alcoholic drinks. EUTM numbers are nine digits with leading zeros, so keep them as strings. Parameters are in the search reference.

Find spellings you didn't type

With RSQL you would write ==OTLY, then ==*OTL*, then every other variant you can think of. Here one request does it:

curl -sG "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=otly" \
  --data-urlencode "offices=EM" \
  --data-urlencode "status_primary=active" \
  --data-urlencode "similarity=identical,fuzzy,phonetic" \
  --data-urlencode "limit=5" \
  | jq -c '.data[] | {mark_text, tier: .match.tier, via: .match.via, number: .application_number, owner: .owners[0].name}'
{"mark_text":"OTLY","tier":"identical","via":["identical","fuzzy","phonetic"],"number":"016403446","owner":"Otly B.V."}
{"mark_text":"Otlo","tier":"near_identical","via":["fuzzy","phonetic"],"number":"018638853","owner":"Norsker Games"}
{"mark_text":"OTY","tier":"near_identical","via":["fuzzy"],"number":"001177468","owner":"OTY S.R.L."}
{"mark_text":"OTL","tier":"near_identical","via":["fuzzy"],"number":"003359528","owner":"Adam Rozanski"}
{"mark_text":"OTY","tier":"near_identical","via":["fuzzy"],"number":"018179737","owner":"Guangdong Yanzhuang Biotechnology Co., Ltd."}

The phonetic channel is off by default; this call turns it on. match.via shows which channel found each hit: Otlo came through fuzzy and phonetic matching, the OTY and OTL marks through fuzzy matching only. Apart from OTLY itself, none of these spellings was in the query. They are search results, not a finding that any two marks conflict.

Look up an EUTM number

curl -s "https://api.signa.so/v1/trademarks/batch" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"identifiers":[{"application_number":"001626233","office_code":"EM"}]}' \
  | jq '.data[0] | {id, mark_text, stage: .status.stage, filing_date, registration_date, expiry_date, owner: .owners[0].name, classes: .nice_classes, office_url}'
{
  "id": "tm_019d47a9-fe99-7251-8f0c-d771fade816d",
  "mark_text": "OATLY",
  "stage": "registered",
  "filing_date": "2000-04-26",
  "registration_date": "2001-06-25",
  "expiry_date": "2030-04-26",
  "owner": "Oatly AB",
  "classes": [
    29,
    32
  ],
  "office_url": "https://euipo.europa.eu/eSearch/#details/trademarks/001626233"
}

The batch endpoint takes up to 100 identifiers per call and returns unmatched inputs in not_found (empty here). office_url deep-links to the same mark in EUIPO's eSearch. Filed in 2000, this OATLY registration is in its third ten-year term.

Compute the renewal deadline

An EUTM lasts ten years from its filing date, not from registration as in the US (more in US vs EU trademark registration). For OATLY that puts expiry on April 26, 2030, despite the 2001 registration date.

curl -s "https://api.signa.so/v1/deadlines/compute" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"id":"tm_019d47a9-fe99-7251-8f0c-d771fade816d"}]}' \
  | jq '.data[0].deadlines[] | {rule_id, type, trigger_date, window_opens, due_date, due_date_adjustment, grace_expiry_date, consequence_if_missed}'
{
  "rule_id": "eu_renewal",
  "type": "renewal",
  "trigger_date": "2000-04-26",
  "window_opens": "2029-10-26",
  "due_date": "2030-04-26",
  "due_date_adjustment": "not_checked",
  "grace_expiry_date": "2030-10-28",
  "consequence_if_missed": "removal_from_register"
}

The renewal window opens six months before expiry, on October 26, 2029. After expiry comes a six-month grace period with a surcharge. Six months lands on Saturday, October 26, 2030, so the period ends on the next working day, Monday, October 28. Miss both the due date and the grace period and the mark is removed from the register.

due_date_adjustment: "not_checked" means EUIPO has not yet published its closure days for 2030, so the due date has not been checked against them.

The rule's sources, from GET /v1/deadline-rules?jurisdiction=EU: EUTMR Article 52 (ten-year term from filing), Article 53(3) (basic and grace periods) and Article 53(8) (consequence), EUTMDR Article 69(1) (the working-day rollover) and EUIPO Guidelines 2026 Part E 7.4. The trademark renewal guide covers the process. Computed dates are a calculation, not legal advice; consult a trademark attorney before relying on one.

Check an opposition window

An opposition is a challenge a third party files against a published application to stop it registering. In the EU the window is three months from publication in the EUTM Bulletin, starting the day after (EUTMR Article 46(1)). EUIPO's API gives you oppositionPeriodEndDate per mark; Signa computes the window from the rule for any publication date:

curl -s "https://api.signa.so/v1/oppositions/compute" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"office":"EM","publication_date":"2026-11-02"}]}' \
  | jq '.data[0] | {rule_id, window_opens, window_closes, status, days_remaining, source: .source.citation}'
{
  "rule_id": "eu_opposition",
  "window_opens": "2026-11-03",
  "window_closes": "2027-02-02",
  "status": "open",
  "days_remaining": 89,
  "source": "EUTMR Article 46(1) — three months from application publication"
}

A mark published on November 2, 2026 can be opposed until February 2, 2027, 89 days from the query date. International registrations designating the EU follow a different rule (eu_opposition_madrid: a one-month offset, then three months, under EUTMR Article 196).

Count filings by year

Counting records per year through RSQL means paging through results 100 at a time. In Signa it is one aggregation:

curl -s "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filters":{"offices":["EM"],"filing_date":{"gte":"2021-01-01","lt":"2026-01-01"}},"options":{"aggregations":["filing_year"]},"limit":1}' \
  | jq -c '.aggregations.filing_year'
{"2021":195301,"2022":168820,"2023":170649,"2024":176564,"2025":191734}

EUIPO trademark records by filing year in Signa, 2021 to 2025

2021 was the highest of the five years at 195,301 records. 2025, at 191,734, is 13.6% above the 2022 low of 168,820. These are Signa record counts at the EUIPO office (Signa data, queried November 5, 2026), including international registrations designating the EU, not EUIPO's official application statistics.

The same in TypeScript

Install with npm install @signa-so/sdk. This ran with version 0.22.0 on Node:

const signa = new Signa({ api_key: process.env.SIGNA_API_KEY });

// 1. Sound-alike search on the EU register
const results = await signa.trademarks.list({
  q: 'otly',
  offices: ['EM'],
  status_primary: 'active',
  similarity: ['identical', 'fuzzy', 'phonetic'],
  limit: 3,
});
for (const hit of results.data) {
  console.log(hit.mark_text, hit.match?.tier, hit.match?.via.join('+'), hit.application_number);
}

// 2. Resolve an EUTM number to a record
const { data } = await signa.trademarks.batch({
  identifiers: [{ application_number: '001626233', office_code: 'EM' }],
});
const mark = data[0];
console.log(mark.id, mark.mark_text, mark.status.stage, mark.expiry_date);

// 3. Its next renewal deadline
const computed = await signa.deadlines.compute({ items: [{ id: mark.id }] });
for (const d of computed.data[0].deadlines) {
  console.log(d.rule_id, d.window_opens, d.due_date, d.grace_expiry_date);
}

// 4. Opposition window for a mark published on 2 November 2026
const windows = await signa.oppositions.compute({
  items: [{ office: 'EM', publication_date: '2026-11-02' }],
});
console.log(windows.data[0].window_opens, windows.data[0].window_closes, windows.data[0].days_remaining);
OTLY identical identical+fuzzy+phonetic 016403446
Otlo near_identical fuzzy+phonetic 018638853
OTY near_identical fuzzy 001177468
tm_019d47a9-fe99-7251-8f0c-d771fade816d OATLY registered 2030-04-26
eu_renewal 2029-10-26 2030-04-26 2030-10-28
2026-11-03 2027-02-02 89

Limits, stated plainly

  • EUIPO is the system of record. Signa is a third-party index. If a decision depends on today's status, check the record's office_url in eSearch.
  • No filing. To file an EUTM from code, use EUIPO's EUTM Filing API.
  • Media. Signa serves mark images but no sound, video or 3D model files; for those, use EUIPO's media endpoints.
  • Rate limit. The Free plan allows 60 requests per minute on most endpoints.
  • Credits. A search costs 10 credits per page, a record read 1, a batch lookup 1 per identifier and a deadline computation 10 per item. See pricing.

Which One to Use

  • EUIPO's APIs: filing applications, reading your User Area mailbox, portfolio actions, the official record, and sound, video or 3D model files. They are free and well specified, and worth the approval wait if you need them.
  • A trademark API such as Signa: the EU register alongside other offices in one schema, similarity search, computed renewal and opposition deadlines, and a key today rather than after an approval. Compare the options in Signa vs the EUIPO API, the best trademark APIs and trademark APIs compared.

Many teams use both: Signa for search and deadlines across offices, EUIPO for filing and the official record.

To run the search example against the EU register, get a Signa API key at app.signa.so (free plan, no approval step) and follow the quickstart.

This article is for information only and is not legal advice. Consult a trademark attorney for legal guidance specific to your situation.