Skip to main content
You are a trademark portfolio strategist at a technology company that sells SaaS products, hardware devices, and consulting services in 8 countries. Your CEO wants to know: “Are we fully protected?” You need to audit your Nice class coverage to identify which goods and services categories are unprotected in which jurisdictions — and prioritize new filings to close the gaps. This guide shows how to perform a class coverage audit using the Signa API.

Prerequisites

  • A Signa API key with trademarks:read, search:read, and portfolios:manage scopes
  • A portfolio containing your marks (see Portfolio Monitoring)
  • A list of Nice classes relevant to your business

1

Define your target coverage matrix

Start by listing the Nice classes your business actually uses and the jurisdictions where you operate. This is your ideal state.
// Define your target coverage
const targetClasses = [
  { class: 9, description: "Software, hardware devices, downloadable apps" },
  { class: 35, description: "Business consulting, retail services, advertising" },
  { class: 36, description: "Financial services, payment processing" },
  { class: 38, description: "Telecommunications, cloud platform services" },
  { class: 41, description: "Training, education, conferences" },
  { class: 42, description: "SaaS, cloud computing, software development" },
];

const targetOffices = ["uspto", "euipo", "ukipo", "cipo", "ipau", "jpo", "cnipa", "dpma"];

console.log(
  `Target: ${targetClasses.length} classes x ${targetOffices.length} jurisdictions = ${targetClasses.length * targetOffices.length} cells`,
);
2

Pull your current registrations and classify them

Fetch all marks in your portfolio, then map each one to the classes and jurisdictions it covers.
# Get all marks in the portfolio (paginate through all)
curl "https://api.signa.so/v1/owners/own_mycompany/trademarks?status_stage[]=registered&status_stage[]=examining&limit=100" \
  -H "Authorization: Bearer $SIGNA_API_KEY"
3

Generate a coverage matrix

Compare your actual coverage against your target to find gaps.
interface GapEntry {
  officeCode: string;
  niceClass: number;
  classDescription: string;
  status: "covered" | "pending_only" | "gap";
  marks: Array<{ id: string; mark_text: string; status: string }>;
}

const matrix: GapEntry[] = [];

for (const office of targetOffices) {
  for (const cls of targetClasses) {
    const marks = coverage[office]?.[cls.class] || [];
    const registeredMarks = marks.filter((m) => m.status === "registered");
    const pendingMarks = marks.filter((m) => m.status !== "registered");

    let status: "covered" | "pending_only" | "gap";
    if (registeredMarks.length > 0) {
      status = "covered";
    } else if (pendingMarks.length > 0) {
      status = "pending_only";
    } else {
      status = "gap";
    }

    matrix.push({
      officeCode: office,
      niceClass: cls.class,
      classDescription: cls.description,
      status,
      marks,
    });
  }
}

// Print the matrix
const gaps = matrix.filter((m) => m.status === "gap");
const pendingOnly = matrix.filter((m) => m.status === "pending_only");
const covered = matrix.filter((m) => m.status === "covered");

console.log("\n=== Coverage Audit Results ===");
console.log(`Total cells: ${matrix.length}`);
console.log(`Covered (registered): ${covered.length} (${((covered.length / matrix.length) * 100).toFixed(0)}%)`);
console.log(`Pending only: ${pendingOnly.length}`);
console.log(`GAPS: ${gaps.length}`);

console.log("\n--- GAPS (no coverage) ---");
for (const gap of gaps) {
  console.log(`  ${gap.officeCode} / Class ${gap.niceClass}: ${gap.classDescription}`);
}
Expected output:
=== Coverage Audit Results ===
Total cells: 48
Covered (registered): 32 (67%)
Pending only: 4
GAPS: 12

--- GAPS (no coverage) ---
  AU / Class 36: Financial services
  AU / Class 38: Telecommunications
  AU / Class 41: Training, education
  JP / Class 35: Business consulting, retail services
  JP / Class 36: Financial services
  JP / Class 38: Telecommunications
  JP / Class 41: Training, education
  CN / Class 36: Financial services
  CN / Class 38: Telecommunications
  CN / Class 41: Training, education
  DE / Class 36: Financial services
  DE / Class 41: Training, education
4

Check Nice class details for gap classes

Before recommending new filings, verify the exact scope of each Nice class to ensure it matches your business activities.
# Get class 36 details
curl https://api.signa.so/v1/classifications/36 \
  -H "Authorization: Bearer $SIGNA_API_KEY"

# Search for specific G&S terms in class 36
curl "https://api.signa.so/v1/classifications/36/terms?q=payment+processing&limit=10" \
  -H "Authorization: Bearer $SIGNA_API_KEY"
5

Prioritize gap closures

Not all gaps are equally urgent. Prioritize based on business revenue in each jurisdiction and the importance of the class.
interface GapPriority {
  jurisdiction: string;
  niceClass: number;
  description: string;
  priority: "critical" | "high" | "medium" | "low";
  reason: string;
}

// Define business priority weights
const officePriority: Record<string, number> = {
  us: 10, eu: 9, gb: 8, ca: 7, de: 6, au: 5, jp: 4, cn: 3,
};

const classPriority: Record<number, number> = {
  9: 10,  // Core product
  42: 10, // Core service
  35: 7,  // Business services
  38: 6,  // Telecom
  36: 5,  // Financial
  41: 3,  // Training
};

const prioritizedGaps: GapPriority[] = gaps.map((gap) => {
  const score =
    (officePriority[gap.officeCode] || 1) *
    (classPriority[gap.niceClass] || 1);

  let priority: "critical" | "high" | "medium" | "low";
  if (score >= 70) priority = "critical";
  else if (score >= 40) priority = "high";
  else if (score >= 20) priority = "medium";
  else priority = "low";

  return {
    jurisdiction: gap.officeCode,
    niceClass: gap.niceClass,
    description: gap.classDescription,
    priority,
    reason: `Score ${score} (jur: ${officePriority[gap.officeCode]}, class: ${classPriority[gap.niceClass]})`,
  };
});

// Sort by priority
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
prioritizedGaps.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);

console.log("\n=== Prioritized Filing Recommendations ===");
for (const gap of prioritizedGaps) {
  console.log(`  [${gap.priority.toUpperCase()}] ${gap.officeCode} / Class ${gap.niceClass}: ${gap.description}`);
}
Expected output:
=== Prioritized Filing Recommendations ===
  [HIGH] JP / Class 35: Business consulting, retail services
  [HIGH] DE / Class 36: Financial services
  [MEDIUM] AU / Class 36: Financial services
  [MEDIUM] AU / Class 38: Telecommunications
  [MEDIUM] CN / Class 36: Financial services
  [MEDIUM] CN / Class 38: Telecommunications
  [MEDIUM] JP / Class 36: Financial services
  [MEDIUM] JP / Class 38: Telecommunications
  [LOW] AU / Class 41: Training, education
  [LOW] JP / Class 41: Training, education
  [LOW] CN / Class 41: Training, education
  [LOW] DE / Class 41: Training, education
6

Check for competitor activity in your gap areas

Before filing, check whether competitors already hold registrations in your gap areas. This informs both the filing strategy and the likelihood of opposition.
# Search for competitor marks in a gap area
curl -X POST https://api.signa.so/v1/trademarks/search \
  -H "Authorization: Bearer $SIGNA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "YourBrandName",
    "strategies": ["exact", "phonetic"],
    "filters": {
      "offices": ["jpo"],
      "nice_classes": [35],
      "status_stage": ["registered", "examining"]
    },
    "options": {
      "aggregations": ["office_code", "status_stage"]
    },
    "limit": 10
  }'

Coverage matrix visualization

Build a simple text matrix to share with stakeholders:
function printMatrix(
  jurisdictions: string[],
  classes: Array<{ class: number }>,
  coverage: CoverageMap,
) {
  // Header
  const header = "     | " + classes.map((c) => `C${c.class}`.padStart(4)).join(" | ");
  console.log(header);
  console.log("-".repeat(header.length));

  for (const jur of jurisdictions) {
    const cells = classes.map((cls) => {
      const marks = coverage[jur]?.[cls.class] || [];
      const hasRegistered = marks.some((m) => m.status === "registered");
      const hasPending = marks.some((m) => m.status !== "registered");

      if (hasRegistered) return "  OK";
      if (hasPending) return " PEN";
      return " GAP";
    });

    console.log(`${jur.padStart(4)} | ${cells.join(" | ")}`);
  }
}

printMatrix(targetOffices, targetClasses, coverage);
Output:
     |   C9 |  C35 |  C36 |  C38 |  C41 |  C42
-----------------------------------------------------
  US |   OK |   OK |   OK |   OK |   OK |   OK
  EU |   OK |   OK |   OK |   OK |  PEN |   OK
  GB |   OK |   OK |   OK |  PEN |   OK |   OK
  CA |   OK |   OK |   OK |   OK |   OK |   OK
  AU |   OK |   OK |  GAP |  GAP |  GAP |   OK
  JP |   OK |  GAP |  GAP |  GAP |  GAP |   OK
  CN |   OK |   OK |  GAP |  GAP |  GAP |   OK
  DE |   OK |   OK |  GAP |   OK |  GAP |   OK

What’s next

Trademark Clearance

Run clearance searches before filing in your gap jurisdictions.

Renewal Management

Ensure existing coverage does not lapse while you work on closing gaps.

Portfolio Monitoring

Set up watches for your newly filed marks as they move through prosecution.