Skip to main content
Learn how to build a trademark validation system for product listings on platforms like Etsy and Amazon.

Overview

This guide shows you how to validate product names against trademark databases before publishing listings, helping sellers avoid infringement issues. What you’ll build:
  • Map product categories to Nice trademark classes
  • Run fast conflict checks before publishing
  • Handle different risk levels appropriately
  • Display clear guidance to sellers
Time to complete: 30 minutes

Use Case: Etsy Store

You’re building a tool for Etsy sellers to check if their product names might infringe on trademarks.

The Problem

Etsy sellers often:
  • Create product names without checking trademarks
  • Get listings taken down for infringement
  • Lose revenue and account standing
  • Don’t know which trademark classes apply to their products

The Solution

Validate product names using the Signa API before publishing.

Step 1: Map Etsy Categories to Nice Classes

Etsy uses its own taxonomy. You need to map these to Nice trademark classes.

Common Etsy Category Mappings

const etsyToNiceClass = {
  // Jewelry & Accessories
  'jewelry': [14],           // Class 14: Jewelry, precious metals
  'accessories': [14, 18],   // Class 18: Leather goods, bags

  // Clothing
  'clothing': [25],          // Class 25: Clothing, footwear, headwear
  'mens_clothing': [25],
  'womens_clothing': [25],

  // Home & Living
  'home_living': [20, 21],   // Class 20: Furniture, Class 21: Household items
  'home_decor': [20, 21],
  'furniture': [20],

  // Art & Collectibles
  'art': [16],               // Class 16: Paper goods, prints
  'prints': [16],

  // Craft Supplies
  'craft_supplies': [16, 26], // Class 16: Paper, Class 26: Sewing supplies

  // Toys & Games
  'toys': [28],              // Class 28: Toys, games, sporting goods

  // Bath & Beauty
  'bath_beauty': [3],        // Class 3: Cosmetics, soaps

  // Electronics
  'electronics': [9]         // Class 9: Electronic devices
};

function getNiceClasses(etsyCategory) {
  return etsyToNiceClass[etsyCategory] || [35]; // Default to Class 35 (retail)
}

Example Usage

// Seller creates a listing in "jewelry" category
const category = 'jewelry';
const classes = getNiceClasses(category); // [14]

// Now we know to check Class 14 trademarks

Step 2: Run Fast Conflict Check

Use the fast conflict check endpoint for quick validation (300-800ms).
async function checkProductName(productName, etsyCategory) {
  const classes = getNiceClasses(etsyCategory);

  const response = await fetch('https://api.signa.so/v1/analysis/check', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.TRADEMARK_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      text: productName,
      classes: classes,
      jurisdictions: ['US', 'EU'] // Check US and EU
    })
  });

  return await response.json();
}

Example Request

const result = await checkProductName(
  'Sterling Silver Moon Necklace',
  'jewelry'
);

Example Response

{
  "risk_level": "LOW",
  "risk_score": 0.23,
  "conflicts": [
    {
      "mark": {
        "text": "MOON JEWELS",
        "id": "tm_us_1234567"
      },
      "similarity_score": 0.42,
      "office": "USPTO",
      "status": "live_registered",
      "classes": [14],
      "reason": "Contains 'moon' but different overall impression"
    }
  ],
  "recommendation": "PROCEED_WITH_CAUTION",
  "analysis_time_ms": 487
}

Step 3: Handle Risk Levels

Show appropriate guidance based on risk level.
function getSellerGuidance(riskLevel) {
  switch(riskLevel) {
    case 'CLEAR':
      return {
        canPublish: true,
        message: '✓ No trademark conflicts found',
        color: 'green',
        action: 'You can publish this listing'
      };

    case 'LOW':
      return {
        canPublish: true,
        message: '⚠ Minor similarities found',
        color: 'yellow',
        action: 'Review potential conflicts before publishing',
        showConflicts: true
      };

    case 'MEDIUM':
      return {
        canPublish: false,
        message: '⚠ Moderate risk of trademark conflict',
        color: 'orange',
        action: 'Consider changing your product name',
        showConflicts: true,
        suggestAlternatives: true
      };

    case 'HIGH':
      return {
        canPublish: false,
        message: '✕ High risk of trademark infringement',
        color: 'red',
        action: 'Do not use this name - choose a different one',
        showConflicts: true,
        blockPublish: true
      };
  }
}

Display to Seller

function ProductNameValidator({ productName, category }) {
  const [result, setResult] = useState(null);
  const [checking, setChecking] = useState(false);

  async function validateName() {
    setChecking(true);
    const checkResult = await checkProductName(productName, category);
    setResult(checkResult);
    setChecking(false);
  }

  const guidance = result ? getSellerGuidance(result.risk_level) : null;

  return (
    <div>
      <input
        value={productName}
        placeholder="Enter product name"
        onChange={(e) => validateName()}
      />

      {checking && <span>Checking trademarks...</span>}

      {guidance && (
        <div className={`alert alert-${guidance.color}`}>
          <p>{guidance.message}</p>
          <p>{guidance.action}</p>

          {guidance.showConflicts && result.conflicts.length > 0 && (
            <div>
              <h4>Potential Conflicts:</h4>
              {result.conflicts.map(conflict => (
                <div key={conflict.mark.id}>
                  <strong>{conflict.mark.text}</strong>
                  <span>Similarity: {(conflict.similarity_score * 100).toFixed(0)}%</span>
                  <p>{conflict.reason}</p>
                </div>
              ))}
            </div>
          )}

          {guidance.blockPublish && (
            <button disabled>Cannot Publish - Trademark Conflict</button>
          )}

          {guidance.canPublish && (
            <button onClick={publishListing}>Publish Listing</button>
          )}
        </div>
      )}
    </div>
  );
}

Step 4: Deep Analysis for High-Risk Cases

For products flagged as MEDIUM or HIGH risk, offer deep analysis.
async function runDeepAnalysis(productName, productDescription, category) {
  const classes = getNiceClasses(category);

  const response = await fetch('https://api.signa.so/v1/analysis/clearance', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.TRADEMARK_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      trademark: productName,
      business_description: productDescription,
      classes: classes,
      offices: ['USPTO', 'EUIPO']
    })
  });

  return await response.json();
}

Show Alternative Suggestions

{result.risk_level === 'HIGH' && (
  <div>
    <button onClick={() => runDeepAnalysis(...)}>
      Get Detailed Analysis & Suggestions
    </button>

    {deepAnalysis && deepAnalysis.alternatives_suggested && (
      <div>
        <h4>Suggested Alternatives:</h4>
        <ul>
          {deepAnalysis.alternatives_suggested.map(alt => (
            <li>
              <button onClick={() => setProductName(alt)}>
                {alt}
              </button>
            </li>
          ))}
        </ul>
      </div>
    )}
  </div>
)}

Complete Example

Here’s a complete working example:
class EtsyListingValidator {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.signa.so/v1';
  }

  async validateListing(listing) {
    const { title, category, description } = listing;

    // 1. Get relevant trademark classes
    const classes = this.getNiceClasses(category);

    // 2. Run fast check
    const quickCheck = await this.fastCheck(title, classes);

    // 3. Handle based on risk
    if (quickCheck.risk_level === 'CLEAR' || quickCheck.risk_level === 'LOW') {
      return {
        approved: true,
        risk: quickCheck.risk_level,
        conflicts: quickCheck.conflicts
      };
    }

    // 4. Run deep analysis for medium/high risk
    const deepAnalysis = await this.deepAnalysis(title, description, classes);

    return {
      approved: false,
      risk: deepAnalysis.risk_level,
      conflicts: deepAnalysis.conflicts,
      alternatives: deepAnalysis.alternatives_suggested,
      recommendation: deepAnalysis.recommendation
    };
  }

  async fastCheck(text, classes) {
    const response = await fetch(`${this.baseUrl}/analysis/check`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ text, classes, jurisdictions: ['US', 'EU'] })
    });
    return response.json();
  }

  async deepAnalysis(trademark, description, classes) {
    const response = await fetch(`${this.baseUrl}/analysis/clearance`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        trademark,
        business_description: description,
        classes,
        offices: ['USPTO', 'EUIPO']
      })
    });
    return response.json();
  }

  getNiceClasses(etsyCategory) {
    const mapping = {
      'jewelry': [14],
      'clothing': [25],
      'home_living': [20, 21],
      'art': [16],
      'bath_beauty': [3],
      'electronics': [9]
    };
    return mapping[etsyCategory] || [35];
  }
}

// Usage
const validator = new EtsyListingValidator(process.env.TRADEMARK_API_KEY);

const listing = {
  title: 'Sterling Silver Moon Necklace',
  category: 'jewelry',
  description: 'Handmade sterling silver necklace with moon pendant'
};

const result = await validator.validateListing(listing);

if (result.approved) {
  console.log('✓ Safe to publish');
} else {
  console.log('✗ Trademark conflict detected');
  console.log('Suggested alternatives:', result.alternatives);
}

Real-World Results

Example 1: Clear Case

// Input
{
  title: "Handmade Ceramic Coffee Mug",
  category: "home_living"
}

// Result
{
  risk_level: "CLEAR",
  conflicts: [],
  approved: true
}
// ✓ Safe to publish

Example 2: Low Risk

// Input
{
  title: "Nike-Style Running Shorts",
  category: "clothing"
}

// Result
{
  risk_level: "LOW",
  conflicts: [
    {
      mark: "NIKE",
      similarity_score: 0.31,
      reason: "Contains 'Nike' in description but clearly comparative"
    }
  ],
  approved: true
}
// ⚠ Review conflicts, but can proceed

Example 3: High Risk

// Input
{
  title: "Tiffany Blue Gift Box",
  category: "home_living"
}

// Result
{
  risk_level: "HIGH",
  conflicts: [
    {
      mark: "TIFFANY BLUE",
      similarity_score: 0.96,
      reason: "Exact match with famous registered color trademark",
      owner: "Tiffany and Company"
    }
  ],
  alternatives: [
    "Turquoise Gift Box",
    "Robin's Egg Blue Box",
    "Aqua Gift Box"
  ],
  approved: false
}
// ✗ Do not use - suggest alternatives

Best Practices

Cache Results

Cache validation results for 24 hours to avoid repeated checks:
const cache = new Map();

async function validateWithCache(title, category) {
  const key = `${title}:${category}`;

  if (cache.has(key)) {
    return cache.get(key);
  }

  const result = await validator.validateListing({ title, category });
  cache.set(key, result);

  return result;
}

Validate on Blur, Not on Every Keystroke

<input
  value={title}
  onChange={(e) => setTitle(e.target.value)}
  onBlur={() => validateListing()}  // Only check when user finishes typing
/>

Show Loading State

{checking && (
  <div className="spinner">
    <span>Checking for trademark conflicts...</span>
    <small>This usually takes under 1 second</small>
  </div>
)}

Cost Analysis

For a typical Etsy seller publishing 100 products/month:
  • Fast checks: 100 × 2 credits = 200 credits
  • Deep analysis (20% of listings): 20 × 5 credits = 100 credits
  • Total: 300 credits/month
On the Starter plan (1,000 credits), this seller could validate 300+ products.

Next Steps

Support

Building something similar for Amazon, Shopify, or another platform? We’d love to help. Contact us at [email protected].