# USPTO Trademark Scraper - TSDR Status by Serial/Reg # (`zet46/uspto-trademark-scraper`) Actor

Live USPTO trademark status from TSDR. Look up by Serial or Registration Number: mark text, status, TM5 live/dead, filing/registration dates, Int. Classes, goods/services, owner, basis flags, prosecution history. Use when an agent needs trademark status, availability, or history. $0.50 per 1,000.

- **URL**: https://apify.com/zet46/uspto-trademark-scraper.md
- **Developed by:** [An Tan Huynh](https://apify.com/zet46) (community)
- **Categories:** Business, AI, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 trademark status rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## USPTO Trademark Scraper

Scrape the live status of any US trademark directly from the official USPTO
TSDR system (`tsdr.uspto.gov`), straight into a structured JSON row. Look up
by **US Serial Number** or **US Registration Number**: the actor pulls the
TSDR status page, parses every labelled field, and emits one record per
unique trademark.

**No API key, no account, no login required.** The TSDR endpoints
`/statusview/sn<SERIAL>` and `/statusview/rn<REGNUM>` serve the full
server-rendered status page over plain HTTPS from an Apify Proxy datacenter
IP. All source code is open and the actor sends polite requests at human
pace with no browser emulation.

**Pricing: $0.50 per 1,000 trademarks** (billed as `status` events — one
charge per unique trademark successfully parsed; HTTP 404 and duplicate
serials are free).

### What you get per trademark

Every row carries the data from the TSDR status page as a flat object:

| Field | Example |
| --- | --- |
| `serialNumber` | `88000001` |
| `registrationNumber` | `6380676` (null if pending/abandoned) |
| `markText` | `SLIM JACS` |
| `markDrawingType` | `4 - STANDARD CHARACTER MARK` |
| `statusShort` | `Registered.` (full sentence) |
| `statusSlug` | `registered` |
| `isLive` | `true` / `false` / null (from TM5 common status) |
| `tm5StatusHeadline` | `LIVE/REGISTRATION/Issued and Active` |
| `filingDate` / `registrationDate` / `publicationDate` | ISO 8601 dates |
| `internationalClasses` | `["031"]` (3-digit Nice classes) |
| `goodsServices` | `Pet food; Pet treats` |
| `currentLocation` / `dateInLocation` | `PUBLICATION AND ISSUE SECTION` / `2021-05-06` |
| `basis` | `{ filedUse, currentlyUse, filedItu, filed44D, filed66A, filedNoBasis, ... }` booleans |
| `ownerName` / `legalEntityType` | `BIL-JAC FOODS INC.` / `CORPORATION` (public record of who owns the mark) |
| `docketNumber` | attorney docket / matter reference string (reference identifier only) |
| `prosecutionHistory` | full timeline of events (date + description + proceeding #) |

Personal data about individuals is deliberately **not** included: no attorney/correspondent names, no addresses, phone, fax or e-mail addresses.

Dates are returned as ISO 8601 (`YYYY-MM-DD`). TM5 common status gives a
machine-friendly LIVE / DEAD boolean (`isLive`) so you can filter at a
glance.

### Example input

```json
{
  "serialNumbers": ["88000001", "97000001"],
  "registrationNumbers": ["6380676"]
}
```

Both lists are optional, but at least one must be non-empty. Values are
normalised before being sent to USPTO (dashes/spaces/leading zeros are
stripped and the serial is zero-padded to 7 digits). If a serial number
already appeared from the `serialNumbers` list, the same mark will not be
re-fetched when it also shows up through `registrationNumbers` — you only
pay once per unique trademark.

### Example output row

```json
{
  "serialNumber": "88000001",
  "registrationNumber": "6380676",
  "markText": "SLIM JACS",
  "statusShort": "Registered.",
  "statusSlug": "registered",
  "statusDate": "2021-06-08",
  "isLive": true,
  "tm5StatusCode": "8",
  "tm5StatusHeadline": "LIVE/REGISTRATION/Issued and Active",
  "filingDate": "2018-06-14",
  "registrationDate": "2021-06-08",
  "internationalClasses": ["031"],
  "goodsServices": "Pet food; Pet treats",
  "ownerName": "BIL-JAC FOODS INC.",
  "basis": {
    "filedUse": false,
    "currentlyUse": true,
    "filedItu": true,
    "currentlyItu": false
  },
  "prosecutionHistory": [
    { "date": "2021-06-08", "description": "REGISTERED-PRINCIPAL REGISTER", "proceedingNumber": null }
  ],
  "sourceUrl": "https://tsdr.uspto.gov/statusview/sn88000001"
}
```

### Use cases

- **Brand monitoring / brand watch.** Feed a list of competitors' or
  clients' serial numbers and run the actor nightly. Diff
  `prosecutionHistory[0].date` + `prosecutionHistory[0].description` from
  the previous run to catch new office actions, suspensions, publications
  or allowances the moment they hit TSDR.
- **Portfolio due diligence.** Supply a legacy registration list ahead of
  a brand acquisition and get the current owner, cancellation status, and
  Section 8/9 renewal dates for each mark in one dataset.
- **Renewal pipeline.** Supply a batch of registration numbers to flag
  marks approaching Section 8/9 maintenance windows via the status and
  `prosecutionHistory` timeline — no attorney contact scraping required.
- **Trademark MCP server.** Expose `markText`, `statusSlug`, `isLive`,
  `internationalClasses` and `prosecutionHistory[0]` to an AI agent so it
  can answer "Is this trademark live? When does it renew? Who owns it?"
  without the Angular TSDR UI in the loop.

### Pricing

| Event | Price |
| --- | --- |
| `status` — one unique trademark parsed | $0.0005 (**$0.50 / 1,000**) |

Only parsed trademarks are billed. Serial numbers that return HTTP 404 at
TSDR, duplicate serials in your input, and failed-to-parse pages are not
charged.

### Data accuracy and limits

- Rows mirror the TSDR status view at request time. TSDR is the official
  USPTO status source and updates in near-real-time once an event is
  entered — the same page the USPTO's own `/statussearch` UI displays.
- `markText` is populated for word / standard-character marks. Pure design
  marks (`Mark Drawing Type` = 2 or 6) have `markText = null` by design.
- `internationalClasses` currently exposes the primary class(es) listed in
  the status view. Marks with multiple additional-class sections list
  every class code; `internationalClassesText` keeps the raw text with
  '- Primary Class' / 'ADDITIONAL CLASS' labels if you need them.
- The TSDR status endpoint occasionally returns HTTP 403 to the same IP in
  a short window. The actor retries up to 4 times on a fresh IP from the
  pool, so transient blocks don't cause data gaps.

### FAQ

**Does this cover TTAB opposition / cancellation proceedings?**
No — this release reads the Trademark Status side of TSDR, not the TTAB
Vue decision index. If demand picks up a dedicated `ttab_decision` mode
will land in a future version.

**Can I search by brand name instead of serial?**
Not in v0.1. USPTO's modern trademark search (`tmsearch.uspto.gov`) is a
front-end Angular SPA with no stable public API, so this actor only
covers the deterministic lookup-by-number path. If you need a name
search, submit the serials you want to monitor and the actor will do the
rest.

**Where do I find the serial / registration number for my mark?**
Both numbers are printed on every USPTO filing receipt and on the
registration certificate. You can also find them in TSDR itself by
searching the mark name — the result page lists serial numbers you can
feed back to this actor.

# Actor input Schema

## `serialNumbers` (type: `array`):

List of US Serial Numbers (as issued by the USPTO when a trademark application is filed). Values are normalised: leading zeros, dashes and spaces are stripped and the serial is zero-padded to 7+ digits before being sent to `tsdr.uspto.gov/statusview/sn<SERIAL>`. Example: \['88000001', '73000085'].

## `registrationNumbers` (type: `array`):

List of US Registration Numbers (assigned by the USPTO once a mark proceeds to registration). Each one is sent to `tsdr.uspto.gov/statusview/rn<REGNUM>`. If the serial from the resulting record was already fetched via `serialNumbers`, the duplicate is skipped. Example: \['6380676'].

## `proxyConfiguration` (type: `object`):

Apify Proxy datacenter group (default) is sufficient — the TSDR status endpoint occasionally returns HTTP 403 to a single IP in a short window, so the scraper retries on a fresh IP from the pool. Residential US proxy can be forced if you need the slowest-but-safest path. No country lock is required.

## Actor input object example

```json
{
  "serialNumbers": [
    "88000001",
    "97000001"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `trademarks` (type: `string`):

All scraped trademark status rows as JSON items (one per row).

## `trademarksCsv` (type: `string`):

The same rows as a flat CSV file.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "serialNumbers": [
        "88000001",
        "97000001"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("zet46/uspto-trademark-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "serialNumbers": [
        "88000001",
        "97000001",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("zet46/uspto-trademark-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "serialNumbers": [
    "88000001",
    "97000001"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call zet46/uspto-trademark-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zet46/uspto-trademark-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/djcR44XumoAg9gtFW/builds/NhIGaPVnySTexa6y8/openapi.json
