Pinakel · Trademark Data API · v1

Integrator Guide

Structured trademark search, lookup and change tracking across national and regional registers, built for docketing systems and IP platforms. Base URL:

https://api.pinakelai.com

Interactive OpenAPI reference: /docs · machine-readable spec: /openapi.json

Authentication

Every data request carries your API key in the X-API-Key header (or as a Bearer token). Keys are issued when your agreement is set up and can be viewed, rotated and monitored in your customer portal.

curl -H "X-API-Key: pk_live_…" \
  "https://api.pinakelai.com/v1/trademarks?application_number=2078049"

Identity: jurisdiction_code, office, uid

(jurisdiction_code, uid) is the stable identity of every record — uid is opaque text (do not assume a format) and is unique within a jurisdiction. jurisdiction_code is the register the record comes from. office is the registering office as the source record states it, and legitimately differs for international records: a Madrid-protocol mark inside the Swiss register has jurisdiction_code: "CH" and office: "WO".

Endpoints

GET/v1/trademarks

Global structured search across every covered jurisdiction. All filter parameters are AND-combined; values inside one comma-separated parameter are OR. At least one filter is required. Responses are an envelope: {"items": […], "next_cursor": "…"|null} — an empty items with HTTP 200 means "no matches", which is an answer, not an error.

GET /v1/trademarks?owner_name_contains=lululemon&jurisdiction=CA,US,EM
GET /v1/trademarks?status=Registered&expiry_date_from=2026-10-01&expiry_date_to=2026-12-31
GET /v1/trademarks?application_number=2078049

GET/v1/{jurisdiction_code}/trademarks

The same search grammar scoped to one register (drop the jurisdiction parameter — the path pins it).

GET /v1/CA/trademarks?registration_number=TMA1164763
GET /v1/CA/trademarks?mark_name_contains=zenith&nice_class=25,35

Search parameters

parametermatchessemantics
application_number
registration_number
office numbersexact. Several offices reuse numbers across series, so expect a list
jurisdictionregisterCSV of codes, up to 20 (global route only)
officeregistering officeCSV — e.g. office=WO&jurisdiction=CH = Madrid designations inside the Swiss register
statusnormalized statusCSV of Registered · Pending · Abandoned · Cancelled · Unknown (case-insensitive)
mark_featuremark typeCSV — Word, Figurative, Combined, Three-dimensional, …
kind_of_markkindCSV — Individual, Collective, Certificate, …
nice_classNice classesCSV of integers; matches records in any listed class
nice_class_allNice classesrecord must carry all listed classes
vienna_codefigurative elementsCSV; full code (27.05.01) or hierarchical prefix (27.05, 27 — prefix needs a companion filter)
mark_namemark textexact, case-insensitive; also matches translations/transliterations we hold
mark_name_startswith
mark_name_contains
mark textprefix / substring, case-insensitive, min 3 chars; requires a jurisdiction scope of ≤10 codes
owner_name (+_startswith, _contains)applicant / owner / holderone parameter covers all proprietor roles — offices disagree on where the current owner is recorded
representative_name (+_startswith, _contains)representative / agent / correspondent of recordsearch a firm's own portfolio. Merged across roles: some offices (e.g. the US) record the responsible attorney only as the correspondent. Note: self-filed marks may list the owner as their own correspondent
owner_countryproprietor countryCSV of ISO codes
international_registration_numberMadrid IR numberexact — finds the national parts of one international registration across registers
priority_numberpriority claim numberexact — links a family across offices via its shared priority claim
designated_countryMadrid designationsCSV of ISO codes — records designating any listed country
application_date
registration_date
publication_date
status_date
expiry_date
termination_date
opposition_period_end
lifecycle datesexact: field=YYYY-MM-DD · range: field_from= and/or field_to= (inclusive, either side may be open)
updated_since
updated_before
Pinakel change timeISO timestamps, half-open interval [since, before)
sort, ordersort: updated_at (default) · application_date · registration_date; order: asc | desc (default)
limit, cursorlimit ≤500 (≤50 with expand=payload); pass back next_cursor verbatim — it is bound to the exact query that produced it
expand=payloadattach the full normalized document to every item
count=cappedadds matched_count (capped at 10,000) + matched_count_capped. Exact totals over the corpus are deliberately not offered
Very broad queries are refused with query_too_broad (400) or stopped with query_timeout (422) after 10 s — both tell you what to narrow. Unknown parameters are rejected, never ignored; q is reserved for a future query language.

GET/v1/{jurisdiction_code}/trademarks/{uid}

Canonical fetch. Store (jurisdiction_code, uid) after the first lookup and use it for everything afterwards. ?expand=payload attaches the full document.

GET/v1/{jurisdiction_code}/changes?since=…  ·  GET/v1/changes?since=…

The docketing sync feed: every record whose content changed at or after since, in change order, keyset-paginated. The global feed covers all jurisdictions in one loop; the scoped feed covers one.

GET /v1/CA/changes?since=2026-08-01&limit=200&expand=payload
GET /v1/changes?since=2026-08-01&cursor=eyJ…&limit=5000

The contract, precisely:

GET/v1/jurisdictions

Every register the API currently serves, with record counts and the daily-pipeline frontier — the machine-readable version of the coverage table below.

GET/v1/usage

Your key's own month-to-date call count, quota and rate limit — poll it freely to watch your consumption.

Response fields & null semantics

Searches and lookups return the record projection; add ?expand=payload for the full normalized document (can be large — up to ~1 MB for history-rich records).

fieldmeaning
jurisdiction_code, uidthe stable identity — always present
officeregistering office per the source record; differs from jurisdiction_code for international (e.g. Madrid 'WO') records
application_number, registration_numberoffice numbers; null when the office has not assigned one (~13% of records lack a registration number — pending and abandoned marks legitimately have none)
mark_namethe mark text as filed
statusnormalized: Registered · Pending · Abandoned · Cancelled · Unknown
kind_of_mark, mark_featurekind (Individual, Collective, Certificate, …) and mark type (Word, Figurative, Combined, …) as normalized enums
tm5_statusthe office's TM5 status string where provided
application_date, registration_date, publication_date, expiry_date, renewal_date, termination_date, opposition_period_endoffice dates; null means the office did not state it (~36% of records have no expiry date). Null is data — we never substitute defaults.
status_datewhen the office last changed the status — the office-side change signal
external_update_dateoffice-side record timestamp, where the office publishes one
record_update_datePinakel pipeline batch date (attribute only — sync on /changes, not this)
nice_classes, vienna_codesNice classification numbers; Vienna figurative-element codes
owner_names, owner_countries, representative_namesnormalized party data — names lowercased (original script AND Latin translation where the office provides one), country codes uppercased. owner_names merges applicant/owner/holder; representative_names merges representative/agent/correspondent. Original names, roles and full addresses live in the payload
international_registration_numbers, priority_numbers, designated_countriesfamily/Madrid linkage keys (uppercased); null where the record carries none
first_seen_atwhen Pinakel first ingested this record
updated_atwhen the record content last changed in Pinakel — the /changes sync key
payloadfull normalized document (only with expand=payload; the key is absent otherwise, never null)

Errors & limits

Every non-2xx response carries one envelope: {"error": {"code": "…", "message": "…", "param": "…"}}code is the machine-readable contract, param names the offending parameter when one exists.

statuscodemeaning
400invalid_parameter · unknown_parameter · missing_filter · reserved_parameter · query_too_broad · cursor_invalid · cursor_query_mismatchmalformed request — the message says exactly what to fix
401unauthorizedmissing or unknown API key
403key_revokedrevoked key
404not_foundno such record / jurisdiction — a real answer, not an error to retry
422query_timeoutquery too broad to answer in 10 s — narrow it (do not blind-retry)
429rate_limited · quota_exceededper-minute rate or monthly quota — see headers
503unavailabletransient service problem — safe to retry shortly

A rate-limit 429 carries Retry-After and X-RateLimit-Limit / X-RateLimit-Remaining; a quota 429 carries X-Quota-Limit / X-Quota-Used (resets at month end). Requests refused with 429 are never counted against your usage. Every served call costs exactly one metered unit, regardless of page size.

Coverage

Live from the serving database — 2 jurisdictions, 76,067 records ready right now. Coverage grows as backfills complete; this table is generated per-request and never overstates.

jurisdictionrecordsdaily frontier
AD49,2662026-09-18
AL26,8012026-09-18
A jurisdiction listed here serves search, lookups and the /changes feed. Jurisdictions not listed return 404 for every lookup — if you need one you don't see, tell us and we'll prioritize its backfill.

Support

Integration questions: support@pinakelai.com · commercial: hello@pinakelai.com