# US Trademark Status (USPTO TSDR) (`openrows/us-trademark-status`) Actor

Look up your own list of US trademarks by serial or registration number and get each mark's current USPTO status, dates, classes and owner from TSDR, one row per mark.

- **URL**: https://apify.com/openrows/us-trademark-status.md
- **Developed by:** [openrows](https://apify.com/openrows) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$6.00 / 1,000 trademark statuses

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

## US Trademark Status (USPTO TSDR)

Look up your own list of US trademarks by serial or registration number and get each mark's current
status from the USPTO: status text and date, TM5 status, filing, publication and registration dates,
international classes with their goods and services, and the current owner.

The data comes from the public status pages of the USPTO's
[Trademark Status & Document Retrieval (TSDR)](https://tsdr.uspto.gov/) system, one page per mark, and is
delivered as a clean dataset you can download as JSON, CSV or Excel, or pull through the API. HTTP-only,
no browser. This Actor is not affiliated with or endorsed by the USPTO.

**Who it is for:** brand owners, in-house counsel and trademark paralegals watching a portfolio, and
anyone who needs the status of a known list of marks — before a renewal deadline, during due diligence,
or on a schedule to catch a change of status.

**What it is not:** a bulk downloader or a trademark search. It looks up the numbers you give it and
nothing else, at most 1,000 per run, one request at a time with at least 3 seconds between them. If you
need the register in bulk, use the USPTO's own bulk data products on the
[USPTO Open Data Portal](https://data.uspto.gov/) — they are the right tool for that, and the USPTO asks
bulk users to use them.

### What data you get

One row per mark (per serial number):

| Field                  | Description                                                                    |
| ---------------------- | ------------------------------------------------------------------------------ |
| `url`                  | The TSDR status page the row was read from                                     |
| `scrapedAt`            | ISO 8601 timestamp of the lookup                                               |
| `caseUrl`              | The interactive TSDR page for the mark                                         |
| `serialNumber`         | Application serial number, 8 digits                                            |
| `registrationNumber`   | Registration number, 7 digits (zero-padded); `null` while not registered       |
| `markText`             | The mark's literal elements; `null` for a design-only mark                     |
| `markType`             | Trademark, Service Mark, Collective Mark, Certification Mark, or a combination |
| `register`             | Principal or Supplemental                                                      |
| `status`               | TSDR's status text (without the website's on-screen instructions)              |
| `statusDate`           | Date of the current status (YYYY-MM-DD)                                        |
| `statusDescriptor`     | TM5 common status, e.g. `LIVE/REGISTRATION/Issued and Active`                  |
| `filingDate`           | Application filing date                                                        |
| `registrationDate`     | Registration date; `null` while not registered                                 |
| `publicationDate`      | Date published for opposition, where TSDR shows one                            |
| `internationalClasses` | Array of `{ classNumber, goodsAndServices, classStatus }`, one entry per class |
| `ownerName`            | Current owner (holder) of the mark as it appears on the register               |
| `ownerLegalEntityType` | CORPORATION, LIMITED LIABILITY COMPANY, INDIVIDUAL, …                          |
| `ownerCity`            | City of the owner's address — organisations only, `null` for individuals       |
| `ownerState`           | State of the owner's address (US addresses) — organisations only               |
| `ownerCountry`         | Country of the owner's address — organisations only                            |
| `ownerCount`           | How many current owners TSDR lists; the owner fields describe the first one    |

Dates are ISO 8601 strings. `classNumber` is a string exactly as the USPTO writes it (`"009"`, `"032"`,
and `"A"`, `"B"` or `"200"` for certification and collective membership marks). In `goodsAndServices`,
the USPTO's own notation is kept: `[..]` marks deleted goods, `((..))` goods not claimed in a Section 15
affidavit, `*..*` added wording.

### Input

| Option                | Type    | Default | Description                                                                |
| --------------------- | ------- | ------- | -------------------------------------------------------------------------- |
| `serialNumbers`       | array   | -       | Serial numbers, 8 digits each (`85569725`)                                 |
| `registrationNumbers` | array   | -       | Registration numbers, 7 digits each (`4185310`; pad older ones: `0022406`) |
| `maxItems`            | integer | `1000`  | Stop after this many results (max 1,000). Also your cost cap.              |

There is no proxy option: the Actor always talks to TSDR directly, as one identified client (see "How it
treats the USPTO's servers").

- Spaces and punctuation are stripped before a number is checked, so `97/123,456` and `4,185,310` work.
- Numbers are validated strictly: a serial number has exactly 8 digits, a registration number exactly 7.
  An invalid number is not looked up and not charged; it is listed, with the reason, in the run summary
  (`lookups.invalidNumbers`) and in the log.
- A number given twice is looked up once. A mark given by both its serial and its registration number is
  returned — and charged — once.
- One run looks up at most **1,000 numbers** (both lists together, after removing invalid and repeated
  ones). A bigger list fails the run before any request is made; split it across runs.
- **An empty input is not free.** With no numbers at all, the Actor looks up three example marks —
  GOOGLE (serial `85569725`), STARBUCKS COFFEE (serial `98137935`) and COCA-COLA (registration
  `0022406`) — and **charges for them like any other result** (three results).

Example input:

```json
{
    "serialNumbers": ["85569725", "98137935"],
    "registrationNumbers": ["0022406", "4185310"]
}
```

### Output example

A real row from a local run on 2026-09-27 (registration number `0022406`):

```json
{
    "url": "https://tsdr.uspto.gov/statusview/rn0022406",
    "scrapedAt": "2026-09-27T10:37:47.880Z",
    "caseUrl": "https://tsdr.uspto.gov/#caseNumber=70022406&caseSearchType=US_APPLICATION&caseType=DEFAULT&searchType=statusSearch",
    "serialNumber": "70022406",
    "registrationNumber": "0022406",
    "markText": "COCA-COLA",
    "markType": "Trademark",
    "register": "Principal",
    "status": "The registration has been renewed.",
    "statusDate": "2022-09-27",
    "statusDescriptor": "LIVE/REGISTRATION/Issued and Active",
    "filingDate": "1892-05-14",
    "registrationDate": "1893-01-31",
    "publicationDate": null,
    "internationalClasses": [
        {
            "classNumber": "032",
            "goodsAndServices": "[ NUTRIENT OR ] TONIC BEVERAGES",
            "classStatus": "ACTIVE"
        }
    ],
    "ownerName": "Coca Cola Company, The",
    "ownerLegalEntityType": "CORPORATION",
    "ownerCity": "Atlanta",
    "ownerState": "GEORGIA",
    "ownerCountry": "UNITED STATES",
    "ownerCount": 1
}
```

A run summary is stored in the run's key-value store under the key `OUTPUT`. Besides the usual counts
(items pushed, requests, failed requests by category, stop reason) it has a `lookups` block that accounts
for every number you gave: how many were invalid, repeated, returned, came back with no record (or while
TSDR was unavailable), failed or were not attempted, with a `notReturned` list naming each number that
produced no row of its own and why, and an `invalidNumbers` list. A `tsdr` block says whether the run was
stopped because TSDR refused requests or looked unavailable.

### Pricing

**Pay per result.** You are charged per result and nothing else: no platform usage, no compute units.
Invalid numbers, numbers with no TSDR record, numbers not attempted, retries and failed requests are
free. An empty input is charged for its three example marks (see "Input"). The current rate is
on the *Pricing* tab of this Actor — that is the only place it is set, so no figure is repeated here.

- `maxItems` caps the number of results, and therefore the cost, of a run.
- The run also stops when it reaches the *maximum total charge* you set for the run in Apify Console
  or through the API.

### Tips

- Schedule the Actor on your portfolio's list to catch status changes; compare `status`, `statusDate`
  and `statusDescriptor` between runs.
- Expect about 3.5 seconds per number (3 seconds of spacing plus the answer): 100 numbers take roughly
  6 minutes, 1,000 roughly an hour.
- Numbers listed under `lookups.notReturned` with `reason: "notAttempted"` were never looked up — the
  run stopped first (see "Limitations"). Run them again later; they were not charged.

### Limitations

- **Your list only.** No search by mark text, owner or class, and no crawling beyond the numbers given.
- **At most 1,000 numbers per run**, looked up one at a time, at least 3 seconds between the end of one
  answer and the next request.
- **Status page only.** Prosecution history, documents, images, assignments and TTAB proceedings are not
  fetched.
- **Numbers TSDR does not hold, and outages.** TSDR answers a number it has no record of with an HTTP
  503 "system unavailable" page — the same page its own website shows for it, and the same page an outage
  produces. Each number is asked **at most twice**: after that page, or after a failed request (another
  5xx, a timeout, a network error, a page that cannot be read), the Actor asks once more straight away.
  If the second ask gets the same page, the number is reported under `lookups.notReturned` with
  `reason: "noRecordOrUnavailable"`; if it fails, with `reason: "failed"`. Either way it cannot tell a bad
  number from an outage by one answer, so after **three such numbers in a row** it looks up one mark
  known to exist (COCA-COLA, registration `0022406`, not charged). If that fails too, TSDR is taken to
  be down: **the run stops**, and the numbers not yet looked up are reported as `notAttempted`. If it
  succeeds, the run goes on and checks again only after a longer streak (6, then 12, …).
- **The run stops when TSDR refuses it.** A 401, 403 or 429 answer is recorded as failed (`blocked`) and
  never asked again. After **three refusals in a row** — the known-mark check included — the run stops
  and the remaining numbers are reported as `notAttempted`. A refused check counts as a refusal, not as
  an outage — see "How it treats the USPTO's servers".
- **Joint owners.** When TSDR lists several current owners, the owner fields describe the first one and
  `ownerCount` says how many there are.
- **Owner location** is given only for organisations (corporations, LLCs, partnerships) and is read from
  the address as TSDR prints it (city, then state for US addresses, then country); for some foreign
  addresses the USPTO record has a district or prefecture in the city position. For an individual, a sole
  proprietorship or any other entity type the three fields are `null`.

**Source caveats.** What the USPTO says about this data:

- The USPTO's online databases "are not designed or intended to be a source for bulk downloads"; bulk
  data is published separately (see [data.uspto.gov](https://data.uspto.gov/)).
- TSDR shows the status as of the moment the page is generated; the record changes as the USPTO acts on
  the file. A row is a snapshot at `scrapedAt`, not a certified copy of the register.

### Data and compliance

This Actor collects **publicly available data from a public register**, and keeps personal data out:

- It fetches TSDR status pages that are reachable without logging in, needs no account or API key, and
  does not bypass access controls.
- **Kept:** the name of the mark's current owner, as the holder of the mark, exactly as it appears on
  the register, with its legal entity type alongside so you can see when the owner is an individual.
  For an organisation (corporation, LLC, partnership) the city, state and country of its address are kept
  too. The mark text is the trademark itself and is kept even when it is a person's name.
- **Never collected:** where an individual owner is located (city, state, country — the fields are
  `null`), or the location of any owner whose entity type is not a known organisation type; any owner's
  street address and postal code; an individual owner's citizenship; any e-mail address or phone or fax
  number; the attorney of record; the correspondent (name, address, e-mail); the domestic representative;
  examining attorneys and other USPTO staff; signatories on filings. These fields are not extracted, and a
  safety net removes them should they ever appear.
- The USPTO asks that its data be credited: "Source: United States Patent and Trademark Office,
  www.uspto.gov". Trademarks in the results belong to their owners.

#### How it treats the USPTO's servers

The USPTO's terms of use say that anyone who "in effect, deny or decrease service by generating
unusually high numbers of database accesses", manually or automated, may be denied access. This Actor is
built to stay far from that:

- one request at a time, at least 3 seconds after the previous answer (robots.txt included), at most
  two asks per mark, no search pages, no documents;
- at most 1,000 numbers per run;
- no proxy, and no option to add one: the USPTO sees one plain, identified client, never a rotating
  pool of addresses;
- a plain User-Agent that names the Actor, and no headers that imitate a browser;
- a 401, 403 or 429 answer is recorded and the request abandoned — never asked again, never from
  another address — and three in a row end the run;
- when three numbers in a row come back without a record or fail, and the known mark cannot be
  fetched either, the run stops instead of working through the rest of the list.

#### How robots.txt is handled

Before the first page on a host is fetched, the Actor fetches that host's `robots.txt` once and obeys
it for the rest of the run. Rules are read for the product token `openrows`, falling back to the `*`
group when the file does not name us, with `*` and `$` wildcards and the standard "longest matching
rule wins" precedence. A `Crawl-delay` set for us is honoured, up to 10 seconds. When a page redirects,
the URL the redirect actually leads to is checked again against its own host's rules before anything is
read from it, so a redirect cannot carry the Actor onto a site, or a path, that its owner puts off limits.

- **Disallowed URLs are never requested.** They are reported under `skipped.robotsDisallowed` in the
  run summary, so you can see exactly how many of your input URLs the site puts off limits.
- **No `robots.txt` (404 or 410) means no rules**, and the host is crawled normally. It is counted as
  `robotsAbsent` in the summary.
- **A `robots.txt` we cannot read means the host is skipped, not crawled.** Any other response — 401,
  403, 429, a 5xx, a timeout, a network failure, or a 200 that turns out to be an error or login page —
  leaves us without the site's rules, and this Actor will not guess. Every URL on that host is skipped
  and counted under `skipped.robotsUnknown`, and the host and the status that stopped us are listed
  under `robots.unknownHosts` in the summary. If a host you supplied returns no results, look there
  first.

**Removal requests.** If you believe this Actor exposes data it should not, open an issue on the Actor's
*Issues* tab and state the serial number(s) concerned. Requests are answered there, and the fields or
records in question are removed.

You are responsible for using the extracted data in line with the USPTO's terms of use and the laws
that apply to you.

### Support

Report bugs and request fields on the *Issues* tab of this Actor. Include the run ID.

# Actor input Schema

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

USPTO application serial numbers, 8 digits each (e.g. 85569725). Spaces and punctuation are stripped, so 85/569,725 works too. If both number lists are empty, three example marks are looked up and charged like any other result.

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

USPTO registration numbers, 7 digits each (e.g. 4185310). Pad older registrations with leading zeros: 0022406. Spaces and punctuation are stripped, so 4,185,310 works too.

## `maxItems` (type: `integer`):

Stop after this many results. You are charged per result, so this is also your cost cap. One run looks up at most 1,000 numbers (serial and registration numbers together); split a bigger list across runs.

## Actor input object example

```json
{
  "serialNumbers": [
    "85569725",
    "98137935"
  ],
  "registrationNumbers": [
    "0022406"
  ],
  "maxItems": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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": [
        "85569725",
        "98137935"
    ],
    "registrationNumbers": [
        "0022406"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("openrows/us-trademark-status").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": [
        "85569725",
        "98137935",
    ],
    "registrationNumbers": ["0022406"],
}

# Run the Actor and wait for it to finish
run = client.actor("openrows/us-trademark-status").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": [
    "85569725",
    "98137935"
  ],
  "registrationNumbers": [
    "0022406"
  ]
}' |
apify call openrows/us-trademark-status --silent --output-dataset

```

## MCP server setup

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

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/ZrbP9IiTD3bXgIabO/builds/ywIs2AgZio5Y7Jbs3/openapi.json
