How to Add Trademark Checks to an E-Commerce Platform

Trademark checks for e-commerce platforms: add automated screening with Signa's API. Score risk, prevent infringement, and monitor new filings in minutes.
10 min read

A trademark infringement lawsuit costs $200K to $2M in legal fees. A programmatic trademark check costs a fraction of a cent per API call. That cost asymmetry alone should make trademark screening standard infrastructure for every e-commerce platform. For most, it isn't.

Why E-Commerce Platforms Need Trademark Checks

Amazon's Brand Registry program has enrolled 700,000+ brands. In 2023, Amazon removed or blocked 7 billion bad listings through its counterfeit prevention tools. Those numbers reflect the scale of a company that can afford to build proprietary screening systems. Most platforms don't have Amazon's resources, but they face the same legal exposure.

The risk is real: under U.S. law, platforms can face contributory trademark infringement (liability for hosting infringing listings after receiving notice, or for providing the means for infringement with knowledge of the activity). Courts have applied this standard to online marketplaces. The bar isn't "you actively infringed," it's "you knew or should have known." Consult a trademark attorney for legal guidance specific to your situation.

Reactive enforcement (waiting for takedown notices and removing listings after brand owners complain) is expensive and adversarial. Every takedown creates friction: the brand owner is frustrated, the seller disputes the removal, and legal has to review each case manually. Scale that to thousands of listings per day and the cost compounds fast.

Trademark screening belongs in the listing pipeline, the same way fraud detection and content moderation do. A brand name check at submission time catches conflicts before they become legal problems. The business case is straightforward: fewer takedowns, lower legal exposure, better seller experience, and a platform that brand owners trust enough to enroll in voluntarily.

The trademark data available through APIs covers 150M+ records across 200+ offices. That's enough to screen a listing against virtually every active trademark in the world, programmatically, in milliseconds.

Architecture: Where Trademark Checks Fit in the Listing Flow

There are two integration points, and most platforms need both.

Pre-listing screening runs synchronously when a seller submits a new product. The seller enters a product name or brand. Your middleware extracts the brand name, runs a trademark check, and returns a decision: approve, flag for review, or block. This is the primary line of defense.

Post-listing monitoring runs asynchronously in the background. A clean listing today could conflict with a trademark filed tomorrow. New applications are submitted daily across every major office. Watches handle this, covered in Step 4.

Here's how the pre-listing flow works:

Performance matters here. Sellers are submitting a listing, not waiting for a background job. Signa's API responds in under 300ms on average, so the trademark check adds negligible latency to the submission flow.

Step 1: Screen Brand Names at Listing Time

The core integration is a single API call. Send the brand name from the listing to Signa's search endpoint with multiple matching strategies.

Why multi-strategy? Because sellers who use infringing names rarely spell them correctly. "ADIDDAS," "NYKE," "GOOCHI" are the kinds of near-misses that exact matching alone won't catch. Phonetic matching catches sound-alikes. Fuzzy matching catches typos and deliberate misspellings.

curl -X POST "https://api.signa.so/v1/trademarks" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "NYKE",
    "strategies": ["exact", "phonetic", "fuzzy"],
    "filters": {
      "nice_classes": [9, 25, 35],
      "status_stage": ["registered", "examining", "published"]
    }
  }'

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

const results = await signa.trademarks.search({
  query: 'NYKE',
  strategies: ['exact', 'phonetic', 'fuzzy'],
  filters: {
    nice_classes: [9, 25, 35],
    status_stage: ['registered', 'examining', 'published'],
  },
});

Two things to note about the filters.

Nice classes are the international system for categorizing the goods and services a trademark covers. There are 45 classes total. For e-commerce platforms, the most relevant are Class 9 (software, electronics), Class 25 (clothing, footwear), and Class 35 (retail services, advertising). Filter by the classes your marketplace covers to avoid false positives from trademarks in unrelated industries.

Status filtering limits results to active marks. A trademark that was abandoned five years ago isn't a risk. Filter to registered (granted marks), examining (pending applications under review), and published (approved and in the opposition period, a 30-day window where anyone can challenge a mark before it registers).

The response includes relevance_score (0-100) for each result, along with the matched mark text, status, Nice classes, and owner information. Higher scores mean closer matches across more dimensions.

For more on building automated trademark searches, see the full search guide.

Step 2: Build a Risk Scoring System

Raw search results aren't actionable at scale. If your platform processes thousands of listings per day, you need automated triage.

The key risk factors are:

  • Match strategy: exact matches are higher risk than phonetic, which are higher risk than fuzzy
  • Trademark status: registered marks carry more weight than examining or published
  • Nice class overlap: a match in the same class as the listed product is more significant than a match in an unrelated class
  • Relevance score: Signa's composite score (0-100) already accounts for similarity across multiple dimensions

Here's a scoring function that combines these signals:

interface TrademarkMatch {
  mark_text: string;
  status: string;
  nice_classes: number[];
  relevance_score: number;
  matched_strategy: string;
}

interface RiskResult {
  level: 'high' | 'medium' | 'low';
  score: number;
  reason: string;
}

function scoreRisk(
  match: TrademarkMatch,
  listingClasses: number[]
): RiskResult {
  let score = match.relevance_score;

  // Boost for exact or phonetic matches
  if (match.matched_strategy === 'exact') score += 20;
  else if (match.matched_strategy === 'phonetic') score += 10;

  // Boost for registered marks
  if (match.status === 'registered') score += 10;

  // Boost for Nice class overlap
  const classOverlap = match.nice_classes.some(c =>
    listingClasses.includes(c)
  );
  if (classOverlap) score += 15;

  // Cap at 100
  score = Math.min(score, 100);

  if (score >= 80) {
    return { level: 'high', score, reason: `Strong match: "${match.mark_text}" (${match.matched_strategy}, ${match.status})` };
  } else if (score >= 50) {
    return { level: 'medium', score, reason: `Potential conflict: "${match.mark_text}" (${match.matched_strategy})` };
  }
  return { level: 'low', score, reason: 'Low similarity or unrelated class' };
}

The decision matrix:

  • High (score >= 80): block the listing automatically. Notify the seller with the conflicting mark.
  • Medium (score 50-79): queue for human review. Include the match details so the reviewer has context.
  • Low (score < 50): auto-approve. Log the results for audit purposes.

These thresholds should be tuned to your platform's risk tolerance. A luxury goods marketplace might set a lower blocking threshold than a general-purpose platform. Consult a trademark attorney for guidance on enforcement thresholds specific to your situation.

For a more comprehensive approach to risk assessment, see how to build a clearance report generator.

Step 3: Enrich High-Risk Matches

For listings flagged as medium or high risk, fetch full trademark details in bulk rather than one at a time.

curl -X POST "https://api.signa.so/v1/trademarks/batch" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ids": ["tm_a1b2c3d4e5f6", "tm_f6e5d4c3b2a1", "tm_1a2b3c4d5e6f"]
  }'
const details = await signa.trademarks.batch({
  ids: ['tm_a1b2c3d4e5f6', 'tm_f6e5d4c3b2a1', 'tm_1a2b3c4d5e6f'],
});

The batch endpoint accepts up to 100 IDs per request and returns full trademark records, including owner details, filing history, and class descriptions.

Next, check whether the trademark owner has a history of enforcement. Active oppositions and proceedings signal an owner who aggressively protects their marks.

curl "https://api.signa.so/v1/trademarks/tm_a1b2c3d4e5f6/proceedings" \
  -H "Authorization: Bearer $SIGNA_API_KEY"
const proceedings = await signa.trademarks.proceedings('tm_a1b2c3d4e5f6');

An owner with multiple active oppositions is more likely to send takedown notices to your platform. Factor this into the risk decision: a medium-risk score against an aggressively enforcing owner might warrant the same treatment as a high-risk score against a passive one.

This enrichment only runs for flagged listings, so it adds no latency to the normal submission path. For a related pattern, see building a brand name checker.

Step 4: Set Up Continuous Monitoring

Pre-listing screening catches conflicts with existing trademarks. But trademark offices receive new applications every day. A listing that was clean on Monday could conflict with a mark filed on Tuesday.

Watches solve this. Signa supports several watch types. For e-commerce platforms, two are most relevant:

  • Similarity watches: alert when a new filing is similar to a specific term
  • Class watches: monitor entire Nice classes for new activity
curl -X POST "https://api.signa.so/v1/watches" \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: watch_electronics_2026Q3" \
  -d '{
    "name": "Electronics brand monitoring",
    "watch_type": "class",
    "query": {
      "version": "v2",
      "filters": {
        "niceClasses": [9, 35],
        "jurisdictions": ["US", "EU"]
      },
      "trigger_events": ["trademark.created", "trademark.status_changed"]
    }
  }'
const watch = await signa.watches.create({
  name: 'Electronics brand monitoring',
  watch_type: 'class',
  query: {
    version: 'v2',
    filters: {
      niceClasses: [9, 35],
      jurisdictions: ['US', 'EU'],
    },
    trigger_events: ['trademark.created', 'trademark.status_changed'],
  },
}, {
  idempotencyKey: 'watch_electronics_2026Q3',
});

Note the Idempotency-Key header. Watch creation is idempotent, so retries won't create duplicates. Filter keys inside query.filters use camelCase (niceClasses, jurisdictions), not snake_case.

The trigger_events field controls what fires the alert. trademark.created catches new filings. trademark.status_changed catches marks that move to registration or publication, stages where enforcement becomes more likely.

When a watch triggers, Signa sends a webhook to your configured endpoint. Your handler can then re-run the risk scoring logic against affected listings and flag any new conflicts for review. For a deeper look at monitoring strategies, see the complete guide to trademark monitoring.

Full Trademark Check Integration

Here's the full flow wrapped into a middleware function:


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

interface ListingDecision {
  approved: boolean;
  risk_level: 'high' | 'medium' | 'low';
  conflicts: Array&lt;{ mark: string; score: number; owner: string }>;
  requires_review: boolean;
}

async function checkListing(
  brandName: string,
  productClasses: number[]
): Promise<ListingDecision> {
  // 1. Search for conflicting marks
  const results = await signa.trademarks.search({
    query: brandName,
    strategies: ['exact', 'phonetic', 'fuzzy'],
    filters: {
      nice_classes: productClasses,
      status_stage: ['registered', 'examining', 'published'],
    },
  });

  if (!results.data.length) {
    return { approved: true, risk_level: 'low', conflicts: [], requires_review: false };
  }

  // 2. Score each match
  const scored = results.data.map(tm => ({
    ...tm,
    risk: scoreRisk(tm, productClasses),
  }));

  const highRisk = scored.filter(s => s.risk.level === 'high');
  const mediumRisk = scored.filter(s => s.risk.level === 'medium');

  // 3. Enrich high-risk matches
  if (highRisk.length > 0) {
    const ids = highRisk.map(s => s.id);
    const details = await signa.trademarks.batch({ ids });
    return {
      approved: false,
      risk_level: 'high',
      conflicts: details.data.map(d => ({
        mark: d.mark_text,
        score: scored.find(s => s.id === d.id)?.risk.score ?? 0,
        owner: d.owner?.name ?? 'Unknown',
      })),
      requires_review: false,
    };
  }

  if (mediumRisk.length > 0) {
    return {
      approved: false,
      risk_level: 'medium',
      conflicts: mediumRisk.map(s => ({
        mark: s.mark_text,
        score: s.risk.score,
        owner: s.owner?.name ?? 'Unknown',
      })),
      requires_review: true,
    };
  }

  return { approved: true, risk_level: 'low', conflicts: [], requires_review: false };
}

A few performance considerations for production:

  • Cache results. If the same brand name is submitted multiple times (common with rejected-then-resubmitted listings), cache the search response for a short TTL. Trademark data changes daily, not hourly.
  • Async enrichment for medium risk. The enrichment step (batch details + proceedings) can run asynchronously. Accept the listing provisionally, run enrichment in the background, and flag it if the enrichment reveals high risk.
  • Batch watch creation off-peak. If you're creating watches for your top product categories, batch them during off-peak hours rather than at request time.

Signa covers 150M+ records across 200+ trademark offices. The same integration works whether your sellers are listing products in the U.S., Europe, or Asia-Pacific. One API, one schema, global coverage.

Sign up for a free Signa API key and add trademark screening to your platform in under an hour.