The Hard Problem: Normalizing Trademark Data Across 11 Offices

Trademark data normalization means mapping 200+ status codes, local-language goods descriptions, and mismatched entities across 11 offices into one API schema.
13 min read

Eleven trademark offices. Roughly 41 million records. Zero shared schema.

Every office built its data model independently. The USPTO assigns numeric status codes (over 200 of them). EUIPO uses 18 uppercase strings. WIPO tracks lifecycle events named things like BIRTH and DEATH. Goods and services descriptions arrive in the filing office's local language. Some offices publish full event histories; others give you a current snapshot and nothing else. And the data keeps changing: 8 offices sync daily, 3 sync weekly.

If you have ever tried merging records from Salesforce, HubSpot, and a custom CRM into a single pipeline, this problem will feel familiar. Trademark data normalization is the same class of problem, scaled across 11 sovereign institutions that owe each other nothing on data formats.

This post covers the four hardest normalization challenges in production: status codes, entity resolution, data freshness, and the resulting schema. The examples come from building and operating a pipeline that processes all 11 sources continuously.

Why Trademark Data Is Uniquely Hard to Normalize

Start with scale. The USPTO holds 14.3 million trademark records, roughly 35% of the 41 million total across all 11 offices. But volume is not the hard part. The hard part is that each office made different architectural decisions about how to represent the same concepts.

Trademark Records by Office (Millions)

Status codes are the sharpest illustration. When a trademark application arrives at the USPTO, the office tracks its progress with numeric codes. Code 630 means "new application, not yet assigned to an examiner." Code 688 means "notice of allowance issued, awaiting statement of use."

Code 800 means "registered and renewed." There are over 200 of these codes, each encoding a specific procedural state.

EUIPO does the same thing differently. Instead of numeric codes, it uses uppercase string identifiers: FILED, UNDER_EXAMINATION, REGISTERED, ENDED. Eighteen total values. Clean, readable, and completely incompatible with the USPTO's system.

WIPO takes a third approach entirely. Rather than a status field, it records lifecycle events. A trademark's lifecycle begins with a BIRTH event and ends with a DEATH event. The events in between represent procedural actions, not states.

Status is just the beginning. Goods and services descriptions, which define what a trademark actually covers, arrive in the office's local language. A Swiss filing describes its goods in German, French, or Italian. A Norwegian filing uses Norwegian.

There is no automatic translation. Machine translation of legal descriptions introduces risk that most systems cannot tolerate.

Event history varies too. The USPTO publishes a detailed prosecution history for each mark. EUIPO provides event records. Other offices give you only the current status, with no trail of how the mark got there.

If your application needs to answer "when did this mark's status last change?", the answer depends entirely on which office issued it.

This is not a one-time ETL job. Trademark data changes daily. New applications arrive, examinations proceed, registrations issue, renewals lapse.

Eight of the 11 offices publish updates on a daily cycle. Three publish weekly. A normalization pipeline that runs once and calls itself done will be stale within hours.

The global filing volume makes this worse, not better. Trademark filings are growing fastest in markets like Singapore, India, and across Southeast Asia. Every new filing jurisdiction that enters the pipeline brings its own schema decisions, its own update cadence, and its own edge cases.

The Status Problem: 200+ Codes Into 4 Axes

Consider three marks, each at roughly the same procedural stage, in three different offices.

At the USPTO, a trademark that has passed examination and is waiting for the applicant to prove use in commerce carries status code 688: "Notice of Allowance was issued, and the applicant must file a Statement of Use or request an Extension of Time to file." That single code packs a lot of information. The mark passed examination. Opposition either did not happen or was resolved. The ball is in the applicant's court.

At EUIPO, a mark at a comparable stage shows REGISTERED. The European system does not have a Statement of Use requirement, so the equivalent procedural position maps to a different concept entirely.

At WIPO, the same stage might be represented as a BIRTH event followed by no DEATH event, with the office's designation status carrying the actual procedural detail. The word "BIRTH" tells you a Madrid designation was created. It tells you almost nothing about where that designation stands today.

A naive approach to normalization would map each raw code to a single canonical string. USPTO 688 becomes "pending." EUIPO REGISTERED becomes "active." WIPO BIRTH becomes... what? "Pending" is not quite right. "Active" is not right either. The event tells you the designation was born, not where it is now.

The single-string approach fails for a deeper reason: trademark status is not one-dimensional. A mark can be registered and simultaneously have an opposition pending against it. It can be active in the register but subject to a cancellation proceeding. Collapsing all of that into one string ("active") loses information that downstream systems need.

A production-grade taxonomy needs multiple independent axes. The approach that holds up uses four:

Primary (4 values): pending, active, inactive, unknown. This is the broadest classification. Is the mark alive in the register or not?

Stage (18 values): The procedural step within the primary status. For a pending mark, stage distinguishes between filed, under_examination, published, and allowed. For an active mark, it distinguishes between registered, renewed, and in_cancellation. Eighteen total values cover the procedural positions across all 11 offices.

Reason (9 values): Why the mark reached its current state. For inactive marks, reason distinguishes between expired, cancelled, abandoned, withdrawn, and others. This is the field that solves the WIPO DEATH problem. A DEATH event can mean cancelled, expired, or invalidated depending on context. The reason axis captures that distinction.

Challenges (array): Active proceedings against the mark. A registered mark with a pending opposition has status.primary: "active", status.stage: "registered", and status.challenges: ["opposition"]. The array can hold multiple values. This is the axis that a single status string cannot represent at all.

OfficeRaw StatusPrimaryStageReasonChallenges
USPTOCode 688pendingallowed
EUIPOREGISTEREDactiveregistered
WIPOBIRTH (no DEATH)pendingdesignated

One critical design decision: raw codes are preserved alongside normalized values. Every normalized record carries status.raw_code and status.raw_label from the source office. A developer who needs the original USPTO code 688 can still access it. Normalization adds structure; it does not discard the original signal.

The Entity Problem: Same Company, Different Names

Procter & Gamble files trademarks in the United States as "The Procter & Gamble Company." In Germany, the same entity files as "Procter & Gamble Service GmbH." In Switzerland, it is "Procter & Gamble International Operations SA." These are three legal entities that share almost no matchable text beyond the words "Procter" and "Gamble."

Naive name matching, comparing owner strings across offices, breaks immediately at this scale. String similarity catches "Procter & Gamble Company" and "Procter & Gamble Co." but misses "P&G International Operations SA" entirely. And multinational companies are just the obvious case. Smaller entities file through local subsidiaries, holding companies, and legal representatives whose names have no textual overlap with the parent.

The harder version of this problem: two unrelated entities with similar names. "Phoenix Technologies Ltd." in Singapore and "Phoenix Tech LLC" in Delaware are probably different companies. A system that links them on name similarity alone creates false connections that pollute every downstream query.

Evidence-based linking works where name matching fails. The strongest signal is the Madrid registration number. The Madrid Protocol (the international trademark filing system that lets applicants designate multiple countries through a single application) assigns one registration number to designations across all member offices. Procter & Gamble's US, German, and Swiss filings may share no text, but if they trace back to the same Madrid registration, the link is definitive.

Shared applicant IDs within an office provide another signal. Portfolio overlap analysis adds a probabilistic layer: if two entities across different offices hold marks for the same brands in the same Nice classes (the international system for categorizing goods and services into 45 numbered groups), the probability of a connection increases with each overlapping mark.

External data sources anchor entity resolution to ground truth. SEC EDGAR filings cover roughly 10,000 public companies with standardized legal names, subsidiaries, and corporate hierarchies. GLEIF (the Global Legal Entity Identifier Foundation) maintains records for approximately 2.5 million legal entities worldwide, each with a unique LEI and relationship data.

The result for a developer querying an entity endpoint is linked records, not name-matched guesses. A single entity query returns trademarks across offices, with the linking methodology visible in the response. When AI agents perform trademark clearance, this entity resolution determines whether three separate search results represent one risk or three.

The Freshness Problem: 11 Clocks, No Consensus

Each trademark office publishes data on its own schedule. The USPTO publishes daily bulk files. EUIPO updates daily. IP Australia syncs daily. Three other offices update weekly. None of them coordinate. A trademark that was granted in one office yesterday might not appear in another office's data feed until next week.

For a normalization pipeline, this creates an asymmetry that a single "last updated" timestamp cannot represent. Three distinct timestamps are needed:

provenance.source_data_date: The date the source office says its data represents. This is the office's own assertion about currency. When the USPTO publishes a daily file dated September 3, that date becomes the source_data_date for every record in that file.

provenance.retrieved_at: When the pipeline fetched the data. This tells you whether the pipeline is lagging behind the source. If source_data_date is September 3 but retrieved_at is September 5, the pipeline was two days behind.

provenance.updated_at: When the normalized record was last modified in the destination database. This can differ from retrieved_at if the raw data was fetched but nothing changed for a particular record.

The difference between these timestamps matters for monitoring systems. If you are tracking a competitor's trademark and need to know whether a status change is real or just a sync delay, you need all three. A record with source_data_date: 2026-09-01 and retrieved_at: 2026-09-03 tells you the data is two days old at the source level. A record with a recent retrieved_at but an old source_data_date tells you the office has not published new data, not that the record is unchanged.

The consistency model follows from this architecture. Detail endpoints (fetching a specific trademark by ID) provide strong read-after-write consistency. If the pipeline processes an update, the next detail request reflects it. Search endpoints, which operate over an index that rebuilds asynchronously, are eventually consistent, typically converging within 30 seconds.

For most applications, this is transparent. For trademark search systems that need to guarantee a just-filed application appears in results, the consistency window matters. The three-timestamp model lets developers make informed decisions about staleness rather than guessing.

What the Normalized Schema Looks Like

After normalization, a trademark record from any of the 11 offices conforms to the same structure. (For a broader overview of what trademark data contains before normalization, see the developer's guide.) Here is a simplified view of the key fields:

{
  "id": "tm_8Kj2mP4xQ1nR",
  "office_code": "US",
  "registration_number": "6547821",
  "filing_date": "2024-03-15",
  "registration_date": "2025-01-22",
  "mark_text": "BRIGHTPATH",
  "status": {
    "primary": "active",
    "stage": "registered",
    "reason": null,
    "challenges": [],
    "raw_code": "800",
    "raw_label": "Registered and renewed"
  },
  "classifications": [
    { "nice_class": 9, "goods_services_text": "Downloadable software..." },
    { "nice_class": 42, "goods_services_text": "Software as a service..." }
  ],
  "owner": {
    "id": "own_4Tn7vB2kL9wX",
    "name": "BrightPath Technologies Inc.",
    "country_code": "US"
  },
  "provenance": {
    "source_data_date": "2026-09-03",
    "retrieved_at": "2026-09-03T14:22:07Z",
    "updated_at": "2026-09-03T14:22:09Z"
  }
}

The same mark filed at EUIPO would carry office_code: "EM", a different raw_code (REGISTERED instead of 800), and potentially different owner entity text. But status.primary, status.stage, classifications, and the rest of the normalized fields are identical in shape and semantics. A developer writing code against this schema writes it once.

To see this in practice, a cross-office search for a mark returns consistent results regardless of the source office:

curl -G "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  --data-urlencode "q=BRIGHTPATH" \
  --data-urlencode "jurisdictions=US,EM,WO"

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

const results = await signa.trademarks.search({
  query: 'BRIGHTPATH',
  filters: { jurisdictions: ['US', 'EM', 'WO'] },
});

for (const mark of results.data) {
  console.log(mark.office_code, mark.status.primary, mark.status.stage);
  // US active registered
  // EM active registered
  // WO pending designated
}

Every result in that response has the same field structure. The status.primary field means the same thing whether the mark comes from USPTO, EUIPO, or WIPO. The classifications array uses the same Nice class integers and goods/services text structure. The owner.id field links to the same entity graph, so a developer can follow up with an entity query to see all of that owner's marks across offices.

This consistency is what makes cross-office analysis possible without per-office code paths. Filtering active marks, counting filings by class, tracking owner portfolios across jurisdictions: each of these queries works against one schema, not eleven. Phonetic matching and other similarity algorithms operate on the normalized mark_text field regardless of source office, which means search quality does not degrade when you expand from one office to eleven.

The hard problem was never the schema design. Any developer can sketch a reasonable canonical trademark object in an afternoon. The hard problem is building the mapping layer that correctly translates 200+ status codes, resolves entities across jurisdictions, and keeps everything fresh on 11 different clocks, then doing it again tomorrow when the data changes.

Explore the normalized data yourself. Signa's API returns a consistent schema across all 11 offices. Start with a free API key at signa.so.