# JPO

Japan Patent Office Patent Information Retrieval API — patents, designs,
and trademarks.

**Auth**: requires `JPO_API_USERNAME` and `JPO_API_PASSWORD` environment
variables (issued by JPO upon registration; corporate users get higher
daily quotas than individual users).

**Availability**: the JPO MCP tools are **env-gated** — they auto-register
on the MCP surface only when both env vars are present. The hosted public
server `mcp.patentclient.com` intentionally does NOT carry these keys per
JPO TOS, so JPO tools are absent there. Private deploys (e.g. law-tools)
turn the surface on by mounting both secrets in their own Cloud Run env.
Library / Python callers (`from patent_client_agents.jpo import ...`)
work in either case as long as the env vars are set in the calling
process.

The MCP surface is **dispatched by `ip_type`** rather than parallel
patent/design/trademark tools — one tool per use case, with a `Literal`
argument that switches between the three IP types.

## MCP tool surface

| Tool | Args | Notes |
|---|---|---|
| `get_jpo_progress` | `application_number, ip_type` | Full status — works for patent/design/trademark |
| `get_jpo_progress_simple` | `application_number, ip_type` | Status without priority/family |
| `get_jpo_priority_info` | `application_number, ip_type` | Paris + domestic priorities |
| `get_jpo_registration_info` | `application_number, ip_type` | Granted-rights record |
| `get_jpo_number_reference` | `number, kind, ip_type` | Cross-reference application/publication/registration |
| `get_jpo_jplatpat_url` | `application_number, ip_type` | J-PlatPat permalink |
| `get_jpo_applicant_by_code` | `applicant_code, ip_type` | 9-digit code → name |
| `get_jpo_applicant_by_name` | `applicant_name, ip_type` | Exact-match name → code |
| `get_jpo_documents` | `application_number, doc_kind, ip_type, parse=True` | Parsed file-history bundle + signed download URL. Pass `parse=False` to skip parsing and return just metadata + URL. |
| `get_jpo_patent_divisional_info` | `application_number` | Patent-only — no design/trademark equivalent |
| `get_jpo_patent_cited_documents` | `application_number` | Patent-only — patent + non-patent citations |
| `get_jpo_pct_national_phase_number` | `number, kind` | Patent-only — PCT lookup |

`ip_type` defaults to `"patent"` everywhere. `doc_kind` defaults to
`"mailed"` (the most useful prosecution-history slice).

## Client

```python
from patent_client_agents.jpo import (
    JpoClient,
    CaseNumberKind,         # 'application' / 'publication' / 'registration'
    PctKind,                # 'international_application' / 'international_publication'
    NumberType,             # numeric codes used inside bibliographyInformation
    parse_document_bundle,  # Shift-JIS XML parser
    DocumentBundle,
    DocumentEntry,
)
```

Library callers still get the per-IP-type methods on `JpoClient` —
`get_patent_progress`, `get_design_progress`, etc. The `ip_type`
dispatch is an MCP-surface concern only.

## Rate limits and quotas

- **10 requests per minute** is the JPO-mandated soft limit (handbook v14
  §3); enforced in-process by `JpoClient`'s rate limiter.
- **Daily caps are per-endpoint**, enforced server-side. Examples:
  - `app_progress` / `app_progress_simple` — 400 / day
  - `divisional_app_info` / `priority_right_app_info` — 30 / day
  - `applicant_attorney_cd` — 200 / day
  - `app_doc_cont_*` (document downloads) — 100 / day
  - `cite_doc_info` — 100 / day
  - `registration_info` — 50 / day
  - `jpp_fixed_address` — 200 / day
- Each response includes `remainAccessCount` (parsed into the `ApiResult`
  envelope but not surfaced on most domain models).
- Daily quota counter resets at 00:00 JST.

## Application number format

JPO patent application numbers are **10 digits** with no separators:
4-digit Gregorian year + 6-digit zero-padded serial. Pass them as
strings without hyphens:

```python
await client.get_patent_progress("2020123456")  # NOT "2020-123456"
```

Design and trademark applications use the same 10-digit format.

## Patent / design / trademark progress

```python
async with JpoClient() as client:
    # Full progress: title, applicants, dates, priority, family, file wrapper
    progress = await client.get_patent_progress("2020123456")
    progress.invention_title       # e.g. "立体配線構造体の製造方法"
    progress.applicant_attorney    # list[ApplicantAttorney]
    progress.filing_date           # "20200720" (YYYYMMDD, no separators)
    progress.publication_number    # JP wareki publication number
    progress.ad_publication_number # AD-converted publication (e.g. "2021090037")
    progress.registration_number   # "" if not yet registered
    progress.priority_right_information   # list[PriorityInfo]
    progress.parent_application_information  # ParentApplicationInfo | None
    progress.divisional_application_information  # list[DivisionalApplicationInfo]
    progress.bibliography_information  # list[BibliographyInformation] (file wrapper)

    # Simplified: omits priority + family info; same daily quota
    simple = await client.get_patent_progress_simple("2020123456")

    # Priority claims (Paris + domestic)
    priorities = await client.get_patent_priority_info("2020123456")

    # Divisional family (patent-only — no design/trademark equivalent)
    div = await client.get_patent_divisional_info("2020123456")

    # Citations: patent + non-patent (patent-only)
    cites = await client.get_patent_cited_documents("2020123456")

    # Registration record (granted patents only; None if not registered)
    reg = await client.get_patent_registration_info("2020123456")

    # Number cross-reference: kind = application / publication / registration
    ref = await client.get_patent_number_reference(
        CaseNumberKind.APPLICATION, "2020123456"
    )

    # J-PlatPat permalink (free public search portal)
    url = await client.get_patent_jplatpat_url("2020123456")

    # PCT national-phase lookup (patent-only)
    nat = await client.get_patent_pct_national_number(
        PctKind.INTERNATIONAL_APPLICATION, "JP2019011858"
    )
```

The same shape applies to designs (`get_design_*`) and trademarks
(`get_trademark_*`) on the client. From MCP, just pass
`ip_type="design"` or `ip_type="trademark"` to the dispatched tools.

## Applicant lookup

```python
async with JpoClient() as client:
    name = await client.get_patent_applicant_by_code("000003207")
    # -> "トヨタ自動車株式会社"

    matches = await client.get_patent_applicant_by_name("トヨタ自動車株式会社")
    # NOTE: exact match only. "トヨタ" alone returns nothing.
```

## Document bundles (parsed contents + download URL)

JPO's three document endpoints — `application_documents` (opinions +
amendments), `mailed_documents` (rejections + decisions), and
`refusal_notices` (rejection-only subset of mailed) — return a ZIP of
file-history files. **Patents ship JPO-flavoured XML; designs and
trademarks ship HTM** (handbook v14 §2(1) — both Shift-JIS encoded).
From MCP, **a single tool covers all nine combinations**:

```python
result = await get_jpo_documents(
    application_number="2020123456",
    doc_kind="refusal",       # or "application" / "mailed"
    ip_type="patent",         # or "design" / "trademark"
    parse=True,               # default — set False to skip parsing
)
# {
#   "ip_type": "patent",
#   "doc_kind": "refusal",
#   "application_number": "2020123456",
#   "entries": [
#     {
#       "filename": "06124084096-jpntce.xml",
#       "document_name": "拒絶理由通知書",
#       "document_variant": "notice-of-rejection-a131-rn",
#       "legal_date": "20240220",
#       "drafter_name": "黒田　久美子",
#       "articles": ["第29条第1項第3号（新規性）", ...],
#       "body_text": "この出願は、次の理由によって…"
#     }
#   ],
#   "binary_attachments": [],
#   "download_url": "https://mcp.patentclient.com/downloads/jpo/documents/patent/2020123456/refusal?key=...",
#   "filename": "jpo_patent_2020123456_refusal.zip",
#   "content_type": "application/zip"
# }
```

If JPO has no documents on file (status 107 / 108) the tool returns
`{}` regardless of the `parse` value. The signed `download_url`
resolves to the raw ZIP — useful for agents that need binary
attachments (drawings, sequence listings, etc.) that the parser
doesn't surface inline.

Pass `parse=False` to skip the parser and return just the bundle
metadata + signed `download_url` (plus `filename`, `content_type`,
`size_bytes`, `expires_at`). The response shape becomes
`{"application_number", "ip_type", "doc_kind", "entries": [],
"binary_attachments": [], "download_url", ...}` — useful when the
agent only needs to hand the URL to a human reviewer or to a separate
processing pipeline.

For library callers, the parsing layer is exposed directly:

```python
from patent_client_agents.jpo import parse_document_bundle

async with JpoClient() as client:
    raw = await client.get_design_application_documents("2020015234")
    bundle = parse_document_bundle(
        raw.zip_bytes,
        doc_kind="application",
        ip_type="design",
        application_number="2020015234",
    )
    for entry in bundle.entries:
        print(entry.document_name, entry.legal_date)
```

### Schema notes

`DocumentBundle.entries` has uniform shape across all nine
(ip_type × doc_kind) combinations:

- **applicant-filed docs** (`doc_kind="application"`) populate
  `document_code` (e.g. `A153` for opinion, `A1523` for amendment),
  `document_variant` (`response-a53` / `amendment-a523`),
  `applicant_names`, `dispatch_number` (the office-action ID being
  responded to), and `body_text`.
- **mailed / refusal docs** populate `document_name` (e.g.
  `拒絶理由通知書`), `legal_date` (drafting date), `drafter_name`
  (examiner), `articles` (statutes cited), and `body_text`. The
  `document_code` is empty — JPO mails docs use `document_name` for
  classification.

`binary_attachments` lists filenames of any non-XML / non-HTM files
in the archive (the parser doesn't decode them; fetch the raw ZIP if
you need them).

## Status codes and error handling

The API returns a `result.statusCode` on every JSON response:

| Code | Meaning | Client behaviour |
|------|---------|------------------|
| 100 | Success | Returns the parsed model |
| 107 | No data for query | Returns `None` / `[]` / `{}` |
| 108 | No document body | Returns empty bundle |
| 111 | Number out of scope | Returns `None` / `[]` / `{}` |
| 203 | Daily limit exceeded | Raises `RateLimitError` |
| 204 | Bad parameter | Raises `ApiError` |
| 208 | Invalid characters in parameter | Raises `ApiError` |
| 210 | Invalid token | Raises `AuthenticationError` (auto-retries once) |
| 212 | Invalid auth credentials | Raises `AuthenticationError` |
| 301 | URL not found | Raises `NotFoundError` |
| 302 / 999 | Server error / timeout | Raises `ApiError` |
| 303 | Concentrated access (transient) | Raises `RateLimitError` — back off and retry |
| 400 | Invalid request | Raises `ApiError` |

For HTTP-level errors, vanilla httpx exceptions propagate (with the
exception of HTTP 429 which becomes `RateLimitError`).

## Quirks worth knowing

- **`case_number_reference` and `pct_national_phase_application_number`
  use descriptive `kind` strings**, not the numeric `NumberType` codes.
  Pass `CaseNumberKind.APPLICATION` (= "application") for the former
  and `PctKind.INTERNATIONAL_APPLICATION` (= "international_application")
  for the latter. The numeric `NumberType` enum (01-12) is for the
  `numberType` field *inside* `bibliographyInformation` rows.
- **`applicant_attorney/{name}` requires an exact match** — partial
  queries return status 107.
- **Document endpoints return raw ZIP bytes**, not JSON. Patents ship
  XML, designs and trademarks ship HTM (handbook v14 §2(1)). Both
  formats are **Shift-JIS encoded** (not UTF-8); the
  `parse_document_bundle` helper decodes both for you, but if you walk
  the ZIP yourself decode each entry with `raw.decode("shift_jis")`
  (cp932 as a tolerant fallback).
- **Document bundles are only available for filings after January
  2019** — matches J-PlatPat's search scope. Older applications still
  surface metadata via `app_progress`, but `app_doc_cont_*` returns
  empty bundles for them.
- **`jpp_fixed_address` returns the URL under key `URL`**, not
  `jplatpatUrl` (the OpenAPI spec is wrong).
- **`cite_doc_info` returns `patentDoc` and `nonPatentDoc` as arrays**,
  not the singleton objects the OpenAPI spec describes.
- **`applicant_attorney_cd` returns a single name string**
  (`applicantAttorneyName`), not a list.
- **`case_number_reference` returns a single object**, not a list.
- The MCP `get_jpo_documents` tool returns parsed entries **and** a
  signed `download_url` for the raw ZIP — agents that need binaries
  the parser doesn't surface should fetch the URL.
