> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signa.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Competitor Intelligence

> Track competitor trademark filings with scheduled polling. Search by owner, monitor new filings by class, surface newcomers, and aggregate filing trends over time.

You are a brand protection analyst at a sportswear company. You need to track what your three main competitors -- NovaSole Inc., VeloGrip Corp., and TrailEdge Ltd. -- are filing across the US and EU. When a competitor files a new mark in your core classes (25, 28, and 35), you want to know within 48 hours.

This guide shows how to build a competitor monitoring pipeline using the Signa API.

## Prerequisites

* A Signa API key with `trademarks:read` scope
* Competitor company names or owner IDs

***

<Steps>
  <Step title="Identify competitor owner records">
    Start by searching for each competitor's owner profile. The owner entity in Signa consolidates all filings under one canonical record, even when name variants exist across offices.

    <CodeGroup>
      ```bash cURL theme={null}
      # Search for a competitor by name
      curl "https://api.signa.so/v1/owners?q=NovaSole&limit=5" \
        -H "Authorization: Bearer $SIGNA_API_KEY"
      ```

      ```typescript TypeScript theme={null}
      import Signa from "@signa-so/sdk";

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

      const competitors = ["NovaSole", "VeloGrip", "TrailEdge"];
      const ownerIds: string[] = [];

      for (const name of competitors) {
        const results = await signa.owners.list({ q: name, limit: 5 });

        if (results.data.length > 0) {
          const owner = results.data[0];
          ownerIds.push(owner.id);
          console.log(`${name} -> ${owner.id} (${owner.name}, ${owner.trademark_count} marks)`);
        } else {
          console.log(`${name} -> not found`);
        }
      }
      ```

      ```python Python theme={null}
      import os
      import requests

      headers = {"Authorization": f"Bearer {os.environ['SIGNA_API_KEY']}"}

      competitors = ["NovaSole", "VeloGrip", "TrailEdge"]
      owner_ids = []

      for name in competitors:
          resp = requests.get(
              "https://api.signa.so/v1/owners",
              params={"q": name, "limit": 5},
              headers=headers,
          ).json()

          if resp["data"]:
              owner = resp["data"][0]
              owner_ids.append(owner["id"])
              print(f"{name} -> {owner['id']} ({owner['name']}, {owner['trademark_count']} marks)")
          else:
              print(f"{name} -> not found")
      ```
    </CodeGroup>

    **Expected output:**

    ```
    NovaSole -> own_ns001 (NOVASOLE INC, 87 marks)
    VeloGrip -> own_vg002 (VELOGRIP CORP, 142 marks)
    TrailEdge -> own_te003 (TRAILEDGE LTD, 53 marks)
    ```

    <Tip>
      If a competitor has subsidiaries or name variants, use the owner detail endpoint to check `aliases[]` for alternate names the API has resolved. This prevents you from tracking an incomplete picture.
    </Tip>
  </Step>

  <Step title="Review each competitor's portfolio profile">
    Pull the full owner detail to understand each competitor's filing patterns, top classes, jurisdictions, and preferred attorneys.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.signa.so/v1/owners/own_ns001 \
        -H "Authorization: Bearer $SIGNA_API_KEY"
      ```

      ```typescript TypeScript theme={null}
      for (const ownerId of ownerIds) {
        const owner = await signa.owners.retrieve(ownerId);

        console.log(`\n=== ${owner.name} ===`);
        console.log(`Total marks: ${owner.stats.trademark_count}`);
        console.log(`Registered: ${owner.stats.registered_count}`);
        console.log(`Registration rate: ${(owner.stats.registration_rate * 100).toFixed(0)}%`);
        console.log(`Jurisdictions: ${owner.stats.jurisdiction_count}`);
        console.log(`Earliest filing: ${owner.stats.earliest_filing}`);
        console.log(`Latest filing: ${owner.stats.latest_filing}`);
      }
      ```

      ```python Python theme={null}
      for owner_id in owner_ids:
          owner = requests.get(
              f"https://api.signa.so/v1/owners/{owner_id}",
              headers=headers,
          ).json()

          stats = owner["stats"]
          print(f"\n=== {owner['name']} ===")
          print(f"Total marks: {stats['trademark_count']}")
          print(f"Registration rate: {stats['registration_rate'] * 100:.0f}%")
          print(f"Jurisdictions: {stats['jurisdiction_count']}")
          print(f"Earliest filing: {stats['earliest_filing']}")
          print(f"Latest filing: {stats['latest_filing']}")
      ```
    </CodeGroup>

    **Expected output:**

    ```json theme={null}
    {
      "name": "NovaSole Inc.",
      "canonical_name": "NOVASOLE INC",
      "stats": {
        "trademark_count": 87,
        "registered_count": 72,
        "registration_rate": 0.83,
        "jurisdiction_count": 8,
        "earliest_filing": "2020-03-04",
        "latest_filing": "2026-02-18"
      }
    }
    ```

    <Note>
      For per-class, per-jurisdiction, or per-year breakdowns, use
      [`GET /v1/trademarks`](/api-reference/trademarks/list-trademarks) with
      `owner_id=<id>` filters (e.g. by `nice_class`, `office_code`, or
      `filing_date_range`) and aggregate the list client-side. The ranked-list
      JSONB fields that used to be embedded on the owner detail response were
      removed in April 2026.
    </Note>
  </Step>

  <Step title="List recent filings by competitor">
    Pull each competitor's most recent filings to see what brands they are pursuing right now.

    <CodeGroup>
      ```bash cURL theme={null}
      # Recent filings from NovaSole in classes 25, 28, 35
      curl "https://api.signa.so/v1/owners/own_ns001/trademarks?nice_classes=25,28,35&sort=-filing_date&limit=10" \
        -H "Authorization: Bearer $SIGNA_API_KEY"
      ```

      ```typescript TypeScript theme={null}
      for (const ownerId of ownerIds) {
        const filings = await signa.owners.trademarks(ownerId, {
          nice_classes: [25, 28, 35],
          sort: "-filing_date",
          limit: 10,
        });

        console.log(`\nRecent filings for ${ownerId}:`);
        for (const tm of filings.data) {
          console.log(
            `  ${tm.mark_text} | ${tm.office_code} | ${tm.status.stage} | Filed ${tm.filing_date} | Classes ${tm.classifications.map(c => c.nice_class).join(",")}`,
          );
        }
      }
      ```

      ```python Python theme={null}
      for owner_id in owner_ids:
          filings = requests.get(
              f"https://api.signa.so/v1/owners/{owner_id}/trademarks",
              params={
                  "nice_classes": "25,28,35",
                  "sort": "-filing_date",
                  "limit": 10,
              },
              headers=headers,
          ).json()

          print(f"\nRecent filings for {owner_id}:")
          for tm in filings["data"]:
              print(f"  {tm['mark_text']} | {tm['office_code']} | {tm['status']['stage']} | Filed {tm['filing_date']}")
      ```
    </CodeGroup>

    **Expected output:**

    ```
    Recent filings for own_ns001:
      NOVASOLE APEX  | uspto | examining  | Filed 2026-02-28 | Classes 25,28
      NOVASOLE STRIDE | euipo | filed      | Filed 2026-01-15 | Classes 25,35
      NOVA TRACTION   | euipo | examining  | Filed 2025-11-20 | Classes 28
    ```
  </Step>

  <Step title="Schedule a weekly competitor pull">
    Rerun the owner-trademarks query from the previous step on a cron schedule (weekly is usually enough) and diff the result against last week's snapshot. Any new `tm_*` IDs are fresh filings worth reviewing.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      async function fetchCompetitorFilings(ownerIds: string[], sinceIso: string) {
        const fresh: any[] = [];
        for (const ownerId of ownerIds) {
          const page = await signa.owners.trademarks(ownerId, {
            filing_date_gte: sinceIso,
            limit: 100,
          });
          fresh.push(...page.data);
        }
        return fresh;
      }

      const lastRun = "2026-03-17T00:00:00Z"; // persist between runs
      const filings = await fetchCompetitorFilings(ownerIds, lastRun);

      console.log(`${filings.length} new filings since ${lastRun}`);
      ```

      ```python Python theme={null}
      def fetch_competitor_filings(owner_ids, since_iso):
          fresh = []
          for owner_id in owner_ids:
              page = requests.get(
                  f"https://api.signa.so/v1/owners/{owner_id}/trademarks",
                  params={"filing_date_gte": since_iso, "limit": 100},
                  headers=headers,
              ).json()
              fresh.extend(page["data"])
          return fresh

      last_run = "2026-03-17T00:00:00Z"  # persist between runs
      filings = fetch_competitor_filings(owner_ids, last_run)
      print(f"{len(filings)} new filings since {last_run}")
      ```
    </CodeGroup>

    <Tip>
      Persist the timestamp of your last successful run -- typically in your job scheduler or a small key-value store -- and use it as `filing_date_gte` on the next run. This keeps each poll cheap and avoids duplicates.
    </Tip>
  </Step>

  <Step title="Watch entire classes for newcomers">
    Competitor-by-competitor polling misses brand-new entrants. Run a class-filtered search over the same window to surface filings from owners you do not yet track.

    <CodeGroup>
      ```typescript TypeScript theme={null}
      const classFilings = await signa.trademarks.list({
        filters: {
          nice_classes: [25, 28],
          offices: ["uspto", "euipo"],
          filing_date: { gte: lastRun },
        },
        limit: 100,
        sort: "-filing_date",
      });

      const knownOwners = new Set(ownerIds);
      const newcomers = classFilings.data.filter((tm) => !knownOwners.has(tm.owner?.id));

      console.log(`${newcomers.length} filings from owners not yet in your tracker`);
      ```

      ```python Python theme={null}
      class_filings = requests.post(
          "https://api.signa.so/v1/trademarks",
          headers={**headers, "Content-Type": "application/json"},
          json={
              "filters": {
                  "nice_classes": [25, 28],
                  "offices": ["uspto", "euipo"],
                  "filing_date": {"gte": last_run},
              },
              "limit": 100,
              "sort": "-filing_date",
          },
      ).json()

      known_owners = set(owner_ids)
      newcomers = [tm for tm in class_filings["data"] if (tm.get("owner") or {}).get("id") not in known_owners]
      print(f"{len(newcomers)} filings from owners not yet in your tracker")
      ```
    </CodeGroup>
  </Step>

  <Step title="Aggregate filing trends over time">
    Use the search API with `aggregations_only` to get filing volume trends without downloading individual records. This is useful for quarterly competitive reports.

    <CodeGroup>
      ```bash cURL theme={null}
      # Get filing volume breakdown for a competitor in the last 3 years
      curl -X POST https://api.signa.so/v1/trademarks \
        -H "Authorization: Bearer $SIGNA_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "query": "",
          "filters": {
            "owner_id": "own_ns001",
            "filing_date": { "gte": "2024-01-01" }
          },
          "options": {
            "aggregations": ["filing_year", "nice_classes", "office_code", "mark_feature_type"],
            "aggregations_only": true
          }
        }'
      ```

      ```typescript TypeScript theme={null}
      const trends = await signa.trademarks.list({
        query: "",
        filters: {
          owner_id: "own_ns001",
          filing_date: { gte: "2024-01-01" },
        },
        options: {
          aggregations: ["filing_year", "nice_classes", "office_code", "mark_feature_type"],
          aggregations_only: true,
        },
      });

      console.log("Filing volume by year:", trends.aggregations.filing_year);
      console.log("Class distribution:", trends.aggregations.nice_classes);
      console.log("Offices:", trends.aggregations.office_code);
      console.log("Mark types:", trends.aggregations.mark_feature_type);
      ```

      ```python Python theme={null}
      trends = requests.post(
          "https://api.signa.so/v1/trademarks",
          headers={**headers, "Content-Type": "application/json"},
          json={
              "query": "",
              "filters": {
                  "owner_id": "own_ns001",
                  "filing_date": {"gte": "2024-01-01"},
              },
              "options": {
                  "aggregations": ["filing_year", "nice_classes", "office_code", "mark_feature_type"],
                  "aggregations_only": True,
              },
          },
      ).json()

      print("Filing volume by year:", trends["aggregations"]["filing_year"])
      print("Class distribution:", trends["aggregations"]["nice_classes"])
      ```
    </CodeGroup>

    **Expected output:**

    ```json theme={null}
    {
      "aggregations": {
        "filing_year": { "2024": 12, "2025": 18, "2026": 7 },
        "nice_classes": { "25": 18, "28": 12, "35": 7 },
        "office_code": { "uspto": 20, "euipo": 12, "ukipo": 5 },
        "mark_feature_type": { "word": 28, "figurative": 7, "combined": 2 }
      }
    }
    ```

    <Tip>
      A sudden increase in figurative marks might indicate a competitor is developing new logos or brand identities -- often a sign of upcoming product launches. Word mark filings in new jurisdictions suggest geographic expansion plans.
    </Tip>
  </Step>
</Steps>

***

## Building a competitive dashboard

Combine the above queries into a weekly report that tracks:

| Metric                                 | Source                                                                       |
| -------------------------------------- | ---------------------------------------------------------------------------- |
| New filings per competitor (this week) | `GET /v1/owners/{id}/trademarks?filing_date_gte=...`                         |
| Class distribution shifts              | `POST /v1/trademarks` with `aggregations_only`                               |
| New jurisdictions entered              | Owner detail `stats.jurisdictions` delta                                     |
| Opposition activity                    | `GET /v1/proceedings?party_owner_id=...`                                     |
| Newly abandoned marks                  | `GET /v1/owners/{id}/trademarks?status_primary=inactive&filing_date_gte=...` |

***

## What's next

<Card title="Trademark Clearance" href="/guides/use-cases/trademark-clearance">
  Run a clearance search against your competitors' marks before launching a new brand.
</Card>

<Card title="Opposition Tracking" href="/guides/use-cases/opposition-tracking">
  Monitor TTAB proceedings where your competitors are involved as parties.
</Card>

<Card title="M&A Due Diligence" href="/guides/use-cases/mna-due-diligence">
  Evaluate a competitor's full IP portfolio if an acquisition is on the table.
</Card>
