Use this repository to query the USPTO Patent Scraper from Node.js. One ES module and Apify's official client turn a search string such as TI/(solid state battery) into an array of US patent objects carrying number, title, abstract, inventors, assignees, CPC codes, kind code, dates and a link to the full document. The heavy lifting runs on Apify against Google Patents' index of US documents, so your Express service, Lambda function or queue worker sends a query, waits and reads JSON. No patent-office credentials live in your app; the only secret is the Apify token, which is about as much paperwork as a patent search should need.
Actor: USPTO Patent Scraper · Publisher: Devil Scrapes · Also in Python: uspto-patents-scraper-python
- US documents only: every search is restricted to US grants and published applications, so a sweep on one company does not fill up with foreign family members of the same invention.
- Search-box qualifiers that work:
TI/for titles,AB/for abstracts,AN/for assignee,IN/for inventor andCPC/for a classification, one qualifier per query, or plain words across title, abstract and claims. - Three orderings: relevance, newest publication first or oldest publication first.
- Grants told apart from applications:
grant_dateis filled only for B1 and B2 kind codes, whilepublication_date,filing_dateandpriority_datearrive as ISOYYYY-MM-DDstrings. - Lists stay lists: inventors, assignees and CPC codes come back as arrays, never as one comma-joined string you have to split.
- Stops where you told it to: the
maxResultscap (up to 2,000 per run) is enforced in the middle of a page, so you never pay for overshoot. - One failed document never sinks the run: a patent page that errors is logged and skipped, it is not billed, and the rest of the result set still lands.
- Every row links home:
patent_urlpoints to the full document on Google Patents for claims, drawings and citations.
Wrap the core call in a route that accepts a query and a limit, start the run, and return the objects to your front end. Users of an R&D or legal-tech tool search patents without leaving your app, and you control how results are cached and displayed.
A scheduled worker runs one AN/ query per watched assignee with newest-first sorting, compares the patent_number values with what is already stored, and posts only the unseen documents to a Slack or Teams webhook. People hear about a new publication the week it appears.
Stream the objects into Elasticsearch, OpenSearch or a Postgres full-text column, keyed by patent_number. Titles and abstracts become searchable in your own stack, and the CPC arrays make good facet filters.
When a CRM or diligence app adds a company, enqueue an assignee search and attach counts of grants and pending publications to the record. It is a cheap, automatic signal of how much IP a target actually holds.
You need an Apify account and an API token from Console → Settings → Integrations. Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below.
git clone https://github.com/devilscrapes/uspto-patents-scraper-javascript.git
cd uspto-patents-scraper-javascript
npm install
export APIFY_TOKEN=your_token_here
node index.js # uses input.json
node index.js my-input.json --out data --max-charge 2The script starts the USPTO Patent Scraper, waits for it to finish, prints a link to the run in Apify Console and saves every result to results.json and results.csv. It exits with code 1 and the run's status message if the run fails.
The integration comes down to these lines. call() returns once the run has completed, and listItems() fetches the default dataset as an array of objects.
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('DevilScrapes/uspto-patents-scraper').call({"searchQuery": "TI/(solid state battery)", "maxResults": 10, "sortBy": "publication_date_desc", "proxyConfiguration": {"useApifyProxy": false}});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);index.js sends input.json to the Actor as is. The sample searches patent titles for a single phrase, newest first, and caps the run at ten documents so a test costs a few cents. In a service you would build this object per request, putting the user's text in searchQuery, a CPC or assignee qualifier when they pick a filter, and a sensible maxResults ceiling.
{
"searchQuery": "TI/(solid state battery)",
"maxResults": 10,
"sortBy": "publication_date_desc",
"proxyConfiguration": {
"useApifyProxy": false
}
}| Field | Type | Required | Default | Allowed values |
|---|---|---|---|---|
searchQuery |
string | yes | "neural network training" |
— |
maxResults |
integer | no | 30 |
— |
sortBy |
string | no | "relevance" |
relevance, publication_date_desc, publication_date_asc |
proxyConfiguration |
object | no | {"useApifyProxy": false} |
— |
Each object is one US document. Arrays are always arrays, dates are YYYY-MM-DD strings or null, and grant_date stays null unless the kind code is B1 or B2. The applicants and ipc_classifications arrays exist in the schema but currently come back empty, so build filters on assignees and cpc_classifications.
| Field | Type | Always present |
|---|---|---|
patent_number |
string | yes |
publication_id |
string | no |
title |
string | yes |
abstract |
string or null | no |
inventors |
array | no |
assignees |
array | no |
applicants |
array | no |
cpc_classifications |
array | no |
ipc_classifications |
array | no |
publication_date |
string or null | no |
grant_date |
string or null | no |
filing_date |
string or null | no |
priority_date |
string or null | no |
kind_code |
string or null | no |
patent_url |
string | yes |
scraped_at |
string | no |
| Event | Price (USD) |
|---|---|
| Actor start | $0.2 |
| Result emitted | $0.003 |
Prices as configured on 2026-08-20. The Actor page always shows the current price. Runs are billed to the Apify account that owns the token. Use --max-charge to cap what a single run can cost.
The script below uses Node.js built-ins only. It groups the saved documents by assignee, counts grants against other publications, records the CPC subclasses each owner works in, and writes a JSON summary your API can serve directly.
import { readFile, writeFile } from 'node:fs/promises';
const rows = JSON.parse(await readFile('results.json', 'utf8'));
const owners = new Map();
for (const row of rows) {
const names = row.assignees?.length ? row.assignees : ['(no assignee listed)'];
for (const name of names) {
const entry = owners.get(name) ?? { name, granted: 0, other: 0, latest: null, subclasses: new Set() };
if (row.grant_date) entry.granted += 1;
else entry.other += 1;
if (row.publication_date && (!entry.latest || row.publication_date > entry.latest)) entry.latest = row.publication_date;
for (const code of row.cpc_classifications ?? []) entry.subclasses.add(code.slice(0, 4));
owners.set(name, entry);
}
}
const summary = [...owners.values()]
.map((entry) => ({ ...entry, subclasses: [...entry.subclasses].sort() }))
.sort((a, b) => b.granted + b.other - (a.granted + a.other));
await writeFile('assignee-summary.json', `${JSON.stringify(summary, null, 2)}\n`);
console.table(summary.slice(0, 10).map(({ name, granted, other, latest }) => ({ name, granted, other, latest })));The USPTO publishes bulk data files and its own search tools, but this Actor gives a backend one small JSON contract for keyword, assignee and CPC search with no account. Pick the bulk files when you need the entire corpus; pick this when you need answers to specific queries.
Call client.actor('DevilScrapes/uspto-patents-scraper').start(input) instead of call(), return right away, and register an Apify webhook for successful runs that points at a second function. That one reads the dataset by its ID and stores the documents.
Yes, with one rule: a query holds plain text or exactly one qualifier such as CPC/ or IN/, and AND or OR between qualifiers is not supported. Validate that in your route, or fan out one run per filter and intersect the arrays in JavaScript.
There is a start fee per run and a per-document charge, listed under Pricing. Cap maxResults in your route, and pass --max-charge to node index.js while you experiment so a broad query cannot run away.
No. Results are limited to US grants and published applications, and patent family members are not returned, so treat each object as a single US document.
Set APIFY_TOKEN first: export the token in the same shell, or put it in a local.envfile your shell loads. Never commit it.- Run finished with
FAILEDorABORTED: open the printed Console link; the run log says why. Input validation errors name the field. - Fewer rows than expected: a
--max-chargecap also limits how many results can be charged. Raise the cap or lower the limits in your input. - Something looks wrong in the data: open an issue on the Actor page so the maintainers see it.
- USPTO Patent Scraper on Apify Store
- Python version of this example
- All Devil Scrapes Actors
- Apify web scraping cookbook
Related Actors:
- USPTO Trademark Scraper — the same office's trademark records, searched by mark, owner or number.
- EUIPO Trademark Search Scraper — European trademark filings for brand-clearance checks next to patent work.
- arXiv Scraper — Search & Export Paper Metadata — research preprints, useful for prior-art and technology-trend context.
MIT licensed example code. The Actor itself runs on Apify and is maintained by Devil Scrapes. Not affiliated with the USPTO or Google.