# EUIPO

Read-only access to the [EUIPO Trademark Search](https://dev.euipo.europa.eu/product/trademark-search_100/api/trademark-search) and [Design Search](https://dev.euipo.europa.eu/product/design-search_100/api/design-search) APIs — all EU trademarks (~2.3M EUTMs + international registrations designating the EU, since 1996) and all Registered Community Designs (~1.5M RCDs, since 2003).

## Auth

OAuth 2.0 client_credentials with scope `uid`. Both APIs share the same dev-portal app. Two-step setup:

1. **Register an App.** Sign up at [dev.euipo.europa.eu](https://dev.euipo.europa.eu) (production) and/or [dev-sandbox.euipo.europa.eu](https://dev-sandbox.euipo.europa.eu) (sandbox — separate auth realm). Each app gives you a `client_id` (shown as "API Key" in the UI; sent in `X-IBM-Client-Id`) and a `client_secret`.
2. **Subscribe.** From the App's Subscriptions tab, subscribe to the "Trademark Search" and "Design Search" products on the Default Plan. **Sandbox subscriptions activate immediately. Production subscriptions require sending identity documents to `docs.apiplatform@euipo.europa.eu`** (passport copy for natural persons; company register excerpt for legal persons).

Set `EUIPO_CLIENT_ID` and `EUIPO_CLIENT_SECRET` in your environment. Optional: `EUIPO_ENV=sandbox` to point the clients at the sandbox host.

```python
import os
os.environ["EUIPO_CLIENT_ID"] = "..."
os.environ["EUIPO_CLIENT_SECRET"] = "..."
os.environ["EUIPO_ENV"] = "sandbox"  # default: production
```

> **Sandbox data caveat.** The sandbox is a frozen historical snapshot plus synthetic test rows (newest TMs are labelled *"EUTM Generated by QC Automated Script"*). API response shapes are identical to production — useful for development and tests — but it cannot answer freshness questions like "what was filed last week."

## Quick Start

### Trademarks

```python
from patent_client_agents.euipo_trademarks import (
    EuipoTrademarksClient,
    SearchTrademarksInput,
    GetTrademarkInput,
    search_trademarks,
    get_trademark,
)

# One-shot search
page = await search_trademarks(SearchTrademarksInput(
    query="wordMarkSpecification.verbalElement==*Apple* and status==REGISTERED",
    size=25,
    sort="applicationDate:desc",
))
print(f"{page.total_elements} matches across {page.total_pages} pages")

# Detail by application number
tm = await get_trademark(GetTrademarkInput(application_number="000428557"))
print(tm.status, tm.application_language)
for gns in tm.goods_and_services:
    print(gns.class_number, gns.terms_in("en"))

# Batch — one client, one token, one cache
async with EuipoTrademarksClient(environment="sandbox") as client:
    for tm_row in page.trademarks:
        full = await client.get_trademark(tm_row.application_number)
        ...
```

### Designs

```python
from patent_client_agents.euipo_designs import (
    SearchDesignsInput,
    GetDesignInput,
    search_designs,
    get_design,
)

# Locarno class 14 (recording / communication equipment), 2024 onwards
page = await search_designs(SearchDesignsInput(
    query="applicationDate>=2024-01-01 and locarnoClasses=in=(14.01,14.02,14.03)",
    sort="applicationDate:desc",
))

# Detail by design number (NNNNNNNNN-NNNN)
d = await get_design(GetDesignInput(design_number="099037115-0001"))
print(d.product_terms_in("en"))
for v in d.views:
    print(v.order, v.image_format)
```

## Query Language — RSQL

EUIPO's `query` parameter is RSQL (FIQL variant). Operators:

| Operator | Meaning | Example |
|---|---|---|
| `==` | Exact | `markFeature==FIGURATIVE` |
| `!=` | Not-equal | `status!=EXPIRED` |
| `=in=` | Set membership | `niceClasses=in=(25,35)` |
| `=out=` | Set non-membership | `niceClasses=out=(40)` |
| `=all=` | All-of | `niceClasses=all=(25,28,40)` |
| `<` / `>` / `<=` / `>=` | Ordered comparisons | `applicationDate>=2024-01-01` |
| `*` | String wildcard | `applicants.name==*Apple*` |
| `and` / `or` / `not` | Booleans (with parens) | `(markFeature==WORD and niceClasses=all=(25))` |

Field paths are dotted (e.g. `wordMarkSpecification.verbalElement`).

## Endpoints

### Trademarks

| Method | Path | Coverage |
|---|---|---|
| GET | `/trademarks` | RSQL search |
| GET | `/trademarks/{applicationNumber}` | Full record (~40 fields) |
| GET | `/trademarks/{applicationNumber}/image` | Mark image (figurative / 3D / colour / position / pattern) |
| GET | `/trademarks/{applicationNumber}/image/thumbnail` | Thumbnail |
| GET | `/trademarks/{applicationNumber}/sound` | Sound mark audio |
| GET | `/trademarks/{applicationNumber}/video` | Multimedia / motion mark video |
| GET | `/trademarks/{applicationNumber}/model` | 3D shape mark model |

### Designs

| Method | Path | Coverage |
|---|---|---|
| GET | `/designs` | RSQL search |
| GET | `/designs/{designNumber}` | Full record |
| GET | `/designs/{designNumber}/views/{order}` | Image of one view (1-indexed angle) |
| GET | `/designs/{designNumber}/views/{order}/thumbnail` | View thumbnail |
| GET | `/designs/{designNumber}/model` | 3D model |

## Functions

### Trademarks

| Function | Description |
|---|---|
| `search_trademarks(SearchTrademarksInput)` | RSQL search |
| `get_trademark(GetTrademarkInput)` | Full record |
| `get_trademark_image(GetTrademarkMediaInput)` | Mark image bytes |
| `get_trademark_image_thumbnail(GetTrademarkMediaInput)` | Thumbnail bytes |
| `get_trademark_sound(GetTrademarkMediaInput)` | Sound bytes |
| `get_trademark_video(GetTrademarkMediaInput)` | Video bytes |
| `get_trademark_model(GetTrademarkMediaInput)` | 3D model bytes |

### Designs

| Function | Description |
|---|---|
| `search_designs(SearchDesignsInput)` | RSQL search |
| `get_design(GetDesignInput)` | Full record |
| `get_design_view(GetDesignViewInput)` | View image bytes |
| `get_design_view_thumbnail(GetDesignViewInput)` | View thumbnail bytes |
| `get_design_model(GetDesignMediaInput)` | 3D model bytes |

## Limits and ToS

- HTTPS only
- 25,000 calls/day per app on the Default Plan
- Page size: 10..100 (HTTP 400 for `size<10`)
- Access token lifetime: 7,200 s — refreshed automatically by the library on 401
- Both `Authorization: Bearer ...` and `X-IBM-Client-Id: ...` are required on every request
- Subscriptions are per-product; production access gated on identity-document review

## MCP Tool Surface

MCP tools register only when both `EUIPO_CLIENT_ID` and `EUIPO_CLIENT_SECRET` are set (matches the JPO / CanLII env-gating pattern). Four tools cover the read path:

`search_euipo_trademarks`, `get_euipo_trademark`, `search_euipo_designs`, `get_euipo_design`.

Media downloads (images, sound, video, 3D models) are library-only in v1.
