Pinakel · Trademark Analytics API · v1

Integrator Guide

Aggregate trademark analytics — filing trends, Nice-class distribution, owner and representative leaderboards — computed over our worldwide corpus and served as JSON, so you can build your own dashboards without running a warehouse. Base URL:

https://api.pinakelai.com/analytics

Interactive OpenAPI reference: https://api.pinakelai.com/analytics/docs · machine-readable spec: /openapi.json

Also on this host: /data — per-record trademark search, lookup and change tracking, sold and keyed separately.

Authentication

Every request carries your API key in the X-API-Key header (or as a Bearer token). Keys are scoped per product: a key sold for analytics returns 403 product_not_enabled on the record API, and vice versa. That is a provisioning state, not a bad credential — do not rotate the key; ask us to enable the product.

curl -H "X-API-Key: pk_live_…" \
  "https://api.pinakelai.com/analytics/v1/kpis?jurisdiction=US"

Coverage: read this before you render a number

The corpus is being loaded register by register. Every response carries a coverage block describing exactly what the figures in it were computed over:

{
  "kpis": { … },
  "coverage": {
    "jurisdiction": "US",
    "rows_loaded": 200000,
    "backfill_status": "in_progress",
    "is_sample": true,
    "last_ingested_date": "2026-09-14"
  }
}
When is_sample is true the figures are indicative, not exhaustive — that register is a partial load, so counts are floors and rankings can move as it completes. Surface this in your UI. It is the one error your users cannot detect for themselves, and the only thing we ask of anyone reselling these numbers.

Live from the warehouse — 119 registers, 4,514,223 records, 119 still sampling. This table is generated per-request and never overstates.

registerrecordsbackfillbasis
AD49,134in_progresssample
AE0not_startedsample
AL26,801in_progresssample
AM47,582in_progresssample
AP8,335in_progresssample
AR50,000in_progresssample
AT50,000in_progresssample
AU50,000in_progresssample
AZ0not_startedsample
BA31,003in_progresssample
BD0not_startedsample
BG50,000in_progresssample
BH50,000in_progresssample
BN50,000in_progresssample
BO0not_startedsample
BQ7,727in_progresssample
BR50,000in_progresssample
BT24,447in_progresssample
BX50,000in_progresssample
BY50,000in_progresssample
BZ0not_startedsample
CA50,000in_progresssample
CH50,000in_progresssample
CL50,000in_progresssample
CN50,000in_progresssample
CO50,000in_progresssample
CR50,000in_progresssample
CU50,000in_progresssample
CY50,000in_progresssample
CZ50,000in_progresssample
DE50,000in_progresssample
DK50,000in_progresssample
DO0not_startedsample
DZ50,000in_progresssample
EC50,000in_progresssample
EE50,000in_progresssample
EG0not_startedsample
EM50,000in_progresssample
ES50,000in_progresssample
FI50,000in_progresssample
FR50,000in_progresssample
GB100,000in_progresssample
GE50,000in_progresssample
GG0not_startedsample
GR0not_startedsample
GT0not_startedsample
HK50,000in_progresssample
HR50,000in_progresssample
HU50,000in_progresssample
ID50,000in_progresssample
IE50,000in_progresssample
IL50,000in_progresssample
IN50,000in_progresssample
IS50,000in_progresssample
IT100,000in_progresssample
JE0not_startedsample
JO50,000in_progresssample
JP100,000in_progresssample
KE0not_startedsample
KG0not_startedsample
KH50,000in_progresssample
KR50,000in_progresssample
KW0not_startedsample
KZ0not_startedsample
LA50,000in_progresssample
LI0not_startedsample
LK0not_startedsample
LT50,000in_progresssample
LV50,000in_progresssample
LY28,552in_progresssample
MC0not_startedsample
MD50,000in_progresssample
ME19,645in_progresssample
MK42,852in_progresssample
MM20,213in_progresssample
MN50,000in_progresssample
MO50,000in_progresssample
MT0not_startedsample
MX50,000in_progresssample
MY50,000in_progresssample
NG0not_startedsample
NI0not_startedsample
NO50,000in_progresssample
NZ50,000in_progresssample
OA50,000in_progresssample
PA50,000in_progresssample
PE0not_startedsample
PG38,465in_progresssample
PH50,000in_progresssample
PL50,000in_progresssample
PR0not_startedsample
PT50,000in_progresssample
RO50,000in_progresssample
RS0not_startedsample
RU50,000in_progresssample
SA50,000in_progresssample
SD44,463in_progresssample
SE50,000in_progresssample
SG50,000in_progresssample
SI50,000in_progresssample
SK50,000in_progresssample
SM4,746in_progresssample
SV0not_startedsample
SX14,751in_progresssample
TH50,000in_progresssample
TR50,000in_progresssample
TT50,000in_progresssample
TW100,000in_progresssample
UA50,000in_progresssample
UG50,000in_progresssample
US200,000in_progresssample
UY50,000in_progresssample
UZ0not_startedsample
VN50,000in_progresssample
WO50,000in_progresssample
WS10,756in_progresssample
XK44,751in_progresssample
ZA0not_startedsample
ZM0not_startedsample

Pagination

List endpoints return {"items": […], "next_cursor": "…"|null, "has_more": true|false}. Pass next_cursor back as cursor for the next page; null means you have reached the end. Cursors are bound to the exact query that produced them, so changing a filter mid-pagination is refused rather than silently mixing result sets — start a new scan instead.

There is deliberately no total-row count. Use has_more to drive "next page", and /v1/search/facets when you need counts: it reports exact totals per status, jurisdiction, year, class and owner for the same filters.

Aggregate endpoints

These take an optional jurisdiction (a 2–4 letter register code, e.g. US); omit it for every register combined. Leaderboards take limit (1–500). Responses are an envelope: {"items": […], "coverage": {…}}.

GET/v1/jurisdictions

Every register we hold analytics for, with counts and year range. The machine-readable coverage table — poll it to discover what is available as the corpus loads.

GET/v1/kpis

Headline totals for a register or the whole corpus: records, live vs dead, distinct owners, registration rate.

GET/v1/filings-by-year

Filing volume per year, split live vs dead. Optional year_from / year_to (1800–2100).

GET/v1/nice-classes

Distribution of filings across the 45 Nice classes.

GET/v1/top-owners

Largest filers by volume, with their live ratio. limit defaults to 50.

GET/v1/law-firms

Representative leaderboard: volume, success rate, median days to registration, distinct clients. limit defaults to 100.

GET/v1/squatting

Owners matching the squatting heuristic — high volume, low registration rate, filings scattered across unrelated classes. A signal to investigate, not a determination.

GET/v1/collateral

Owners with security interests recorded against their marks.

GET/v1/search

Structured search across the corpus. q plus search_in (name, owner, rep, keyword, all), include_translations, and filters for owner_key, nice_class, status, jurisdiction, year_from/year_to. Keyset pagination via cursor.

Search is the expensive call — around two orders of magnitude above any aggregate endpoint, and it is permitted to run unbounded over the whole corpus. A jurisdiction or year_from/year_to filter prunes it 5–25×, because the warehouse is partitioned by filing year and clustered on register. Use them whenever your case allows.

GET/v1/search/facets

Breakdowns over the same filters as /v1/search: status, jurisdiction, year, Nice class and top owners.

GET/v1/renewals

Upcoming renewal deadlines, soonest first. months_ahead (1–120), owner_key, nice_class, jurisdiction. Served from a pre-built aggregate, so it is far cheaper than the equivalent search.

GET/v1/dead-marks

Marks the register no longer treats as live. Same grammar as /v1/search, filtered to dead.

GET/v1/owners/{owner_key}

One owner's portfolio. ?include=filings_by_year,jurisdictions,status adds breakdowns.

GET/v1/brands/{brand_key}

One brand name across registers and owners. ?include=owners adds the owner list.

owner_key and brand_key are opaque. Take them from a search hit or a facet and pass them back verbatim — do not construct or parse them. They are normalized forms produced by our pipeline, and treating them as opaque is what lets us improve that normalization without breaking your integration.

GET/v1/usage

Your key's own month-to-date call count, quota and rate limit, broken down per product, so you can watch consumption without asking us.

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_parametermalformed request — the message says exactly what to fix
401unauthorizedmissing or unknown API key
403key_revokedrevoked key
403product_not_enabledvalid key, but it was not sold analytics — do not rotate the key, ask us to enable it
404not_foundno such path — check the /analytics prefix
422query_too_broadthe query would scan more data than the endpoint allows — narrow it with a jurisdiction filter (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.

Analytics figures are served from pre-computed aggregates and are cached, so repeated identical calls are cheap and fast. Aggregates refresh as backfills land; coverage.last_ingested_date tells you how current a register's numbers are.

Support

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