# Product & Vehicle Recalls API — CPSC, NHTSA, FDA, USDA (`insight.solutions/product-recalls-api`) Actor

Every US recall a federal agency publishes, in one schema: consumer products (CPSC), vehicles (NHTSA), drugs, devices and food (FDA), and meat, poultry and egg (USDA FSIS) — plus a VIN decoder that joins each VIN to its model's recalls, complaints and NCAP crash-test ratings. Keyless .gov sources.

- **URL**: https://apify.com/insight.solutions/product-recalls-api.md
- **Developed by:** [Insight Solutions](https://apify.com/insight.solutions) (community)
- **Categories:** Business, E-commerce, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.42 / 1,000 recall or rating records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Product & Vehicle Recalls API — CPSC, NHTSA, FDA, USDA

Every recall a US federal agency publishes, in **one schema**: consumer products
from the **Consumer Product Safety Commission**, vehicles from the **National
Highway Traffic Safety Administration**, drugs, devices and food from the **Food
and Drug Administration**, and meat, poultry and egg products from **USDA's Food
Safety and Inspection Service**. Plus NHTSA owner complaints, NCAP crash-test
ratings, and the free **vPIC VIN decoder** — with each VIN joined to its model's
recalls.

Read straight from the agencies' own APIs. No aggregator sits in the middle, no
API key is required, and no browser is involved.

> **The one thing to know up front.** Recalls, complaints and safety ratings are
> matched by **model year, make and model — not by VIN range**. NHTSA publishes
> no public VIN-range endpoint, so a recall covering part of a production run
> appears against every VIN of that model. Every vehicle row says so, and so does
> every summary row. Treat a match as *"this recall may apply to this car"*, and
> check the campaign against the manufacturer before acting on it.

### At a glance

**Input** — this is the Store prefill; paste it and run:

```json
{ "sources": ["cpsc", "fda"], "keywords": ["battery"], "vehicles": ["2020 Honda Civic"], "includeRatings": true, "maxRecords": 10 }
```

**Output** — one row per record (recall, owner complaint, crash-test rating or
decoded VIN), all in the same columns; the fields you will use most are
`rowType`, `agency`, `title`, `date`, `recallingFirm`, `hazard` and `sourceUrl`
(full list under *Output reference*). Anything that could not be fetched comes
back as a free diagnostic row (`ok: false`, `errorType`, `error`) instead of a
charge.

**Price** — $0.70 per 1,000 recalls and crash-test ratings on the FREE tier
(+ $0.001 per run), $0.15 per 1,000 owner complaints, $0.50 per 1,000 VINs
decoded; diagnostic rows, summary rows, filtered-out records and undecodable
VINs are all free. Pay-per-event, no API key, no browser, limited permissions —
works over the Apify MCP server (`mcp.apify.com`) and with agentic (x402)
payments.

**From code** — `client.actor("insight.solutions/product-recalls-api").call(run_input={…})`
with `apify-client`, or `POST https://api.apify.com/v2/acts/insight.solutions~product-recalls-api/run-sync-get-dataset-items`.

***

### What you get

One row per record. Same columns whoever published it:

```json
{
  "rowType": "recall",
  "agency": "CPSC",
  "source": "cpsc",
  "sourceDataset": "cpsc/recall",
  "recordId": "cpsc:10967",
  "recallNumber": "26761",
  "title": "Cade California Electronic Recalls Finger Light Toys Due to Risk of Serious Injury or Death from Battery Ingestion; Violate Mandatory Standard for Toys",
  "date": "2026-09-10",
  "recallingFirm": "California Cade Electronic, LLC, of Hacienda Heights, California",
  "productNames": ["Cade California Electronic Projecting Finger Light Toys"],
  "productType": "Lights & Accessories",
  "unitsText": "About 179,739",
  "units": 179739,
  "hazard": "The recalled finger lights violate the mandatory safety standard for toys because the toys contain button cell batteries that can be easily accessed by children…",
  "remedyOptions": ["Refund"],
  "soldAt": "Amazon.com from March 2015 through July 2026 for between $5 and $16.",
  "injuriesText": "None reported.",
  "manufacturerCountries": ["China"],
  "images": [{ "url": "https://www.cpsc.gov/s3fs-public/cade-1_0.jpg", "caption": "Recalled LED Projecting Finger Lights" }],
  "proxyPath": "direct",
  "sourceUrl": "https://www.cpsc.gov/Recalls/2026/Cade-California-Electronic-Recalls-Finger-Light-Toys"
}
```

…and the same row for a vehicle recall, with the fields only NHTSA has:

```json
{
  "rowType": "recall",
  "agency": "NHTSA",
  "recordId": "nhtsa:21V215000|HONDA|CIVIC|2020",
  "campaignNumber": "21V215000",
  "date": "2021-03-25",
  "vehicleKey": "2020|HONDA|CIVIC",
  "vehicleMake": "HONDA",
  "vehicleModel": "CIVIC",
  "vehicleModelYear": "2020",
  "manufacturer": "Honda (American Honda Motor Co.)",
  "component": "FUEL SYSTEM, GASOLINE:DELIVERY:FUEL PUMP",
  "summary": "Honda (American Honda Motor Co.) is recalling certain 2019-2020 Acura MDX… The low-pressure fuel pump inside the fuel tank may fail.",
  "consequence": "Fuel pump failure can cause an engine stall while driving, increasing the risk of a crash.",
  "remedy": "Honda will notify owners, and dealers will replace the fuel pump assembly, free of charge.",
  "parkIt": false,
  "parkOutSide": false,
  "overTheAirUpdate": false,
  "sourceUrl": "https://www.nhtsa.gov/recalls?nhtsaId=21V215000"
}
```

…a decoded VIN:

```json
{
  "rowType": "vin",
  "agency": "NHTSA",
  "vin": "1HGCV1F34LA012345",
  "vinValid": true,
  "vinMake": "HONDA",
  "vinModel": "Accord",
  "vinModelYear": "2020",
  "trim": "Sport",
  "bodyClass": "Sedan/Saloon",
  "driveType": "4x2",
  "displacementL": "1.5",
  "engineHP": "192",
  "engineModel": "L15BE",
  "transmissionStyle": "Continuously Variable Transmission (CVT)",
  "plantCity": "MARYSVILLE",
  "plantCountry": "UNITED STATES (USA)",
  "gvwr": "Class 1C: 4,001 - 5,000 lb (1,814 - 2,268 kg)",
  "vinErrorCodes": ["1"],
  "vinErrorTexts": ["1 - Check Digit (9th position) does not calculate properly"],
  "vehicleKey": "2020|HONDA|ACCORD"
}
```

…and a crash-test rating:

```json
{
  "rowType": "rating",
  "agency": "NHTSA",
  "vehicleDescription": "2020 Honda CIVIC 2 DR FWD",
  "overallRating": "5",
  "overallRatingStars": 5,
  "overallFrontCrashRating": "4",
  "overallSideCrashRating": "5",
  "rolloverRating": "5",
  "rolloverPossibility": 0.093,
  "dynamicTipResult": "No Tip",
  "complaintsCount": 224,
  "recallsCount": 5,
  "media": [{ "kind": "video", "type": "front", "url": "https://static.nhtsa.gov/crashTest/videos/2020/v09808C018.wmv" }]
}
```

Owner complaints, free per-source and per-vehicle summary rows, and free
diagnostic rows for anything that could not be read, all carry the same columns.

***

### Quick start

Search every agency for a product:

```json
{
  "keywords": ["lithium battery"],
  "sources": ["cpsc", "fda", "fsis"],
  "maxRecords": 100
}
```

Everything wrong with one car:

```json
{
  "vehicles": ["2020 Honda Civic"],
  "includeComplaints": true,
  "includeRatings": true,
  "maxComplaintsPerVehicle": 100
}
```

Decode a list of VINs and see what each one is recalled for:

```json
{
  "vins": ["1HGCV1F34LA012345", "WBA3A5C51CF256581"],
  "includeRatings": true
}
```

Watch for new Class I food recalls every morning:

```json
{
  "sources": ["fda", "fsis"],
  "classifications": ["Class I"],
  "incremental": true
}
```

You do not have to list `nhtsa` in `sources` when you set `vehicles` or `vins` —
it is switched on for you, because vehicle records exist nowhere else.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `sources` | array | all four | `cpsc`, `nhtsa`, `fda`, `fsis` |
| `keywords` | array | — | Match title, product, description, hazard or reason |
| `manufacturers` | array | — | Match recalling firm, manufacturer, importer, distributor, retailer, USDA establishment |
| `hazardKeywords` | array | — | Match the hazard, consequence or reason |
| `remedyKeywords` | array | — | Match the remedy |
| `states` | array | — | Two-letter codes, matched on the firm's state and the distribution text |
| `classifications` | array | — | `Class I`, `Class II`, `Class III`, `Public Health Alert` |
| `recallIds` | array | — | CPSC recall IDs, or NHTSA campaign numbers like `21V215000` |
| `dateFrom` / `dateTo` | string | — | ISO dates. Server-side at CPSC and FDA, here for NHTSA and USDA |
| `vehicles` | array | — | `"2020 Honda Civic"` or `{"make","model","year"}` |
| `vins` | array | — | VINs, full or partial (`*`), 50 per request |
| `vinModelYear` | string | — | Model-year hint for a partial VIN |
| `decodeOnly` | boolean | `false` | Decode VINs and stop — no recall lookup |
| `includeRawVin` | boolean | `false` | Attach all 154 vPIC fields as `vpicRaw` |
| `includeComplaints` | boolean | `false` | Fetch NHTSA owner complaints |
| `maxComplaintsPerVehicle` | integer | `200` | Cap on complaints per vehicle |
| `includeRatings` | boolean | `false` | Fetch NCAP crash-test ratings |
| `maxRatingVariantsPerVehicle` | integer | `5` | Cap on rated body styles per vehicle |
| `includeCampaignDetail` | boolean | `false` | Fill `potentialUnitsAffected` (one extra request per campaign) |
| `crashOnly` / `fireOnly` | boolean | `false` | Complaints NHTSA flagged as a crash or a fire |
| `resolveAmbiguous` | string | `diagnostic` | `diagnostic`, `first` or `all` |
| `cpscSearchFields` | array | title, product, description | Which CPSC fields a keyword is searched against |
| `maxCpscRequests` | integer | `12` | Request budget for saferproducts.gov |
| `fsisServerSideFilter` | boolean | `true` | Try USDA's text filters before the full list |
| `fdaSearch` | string | — | Raw openFDA `search=` expression, ANDed with the rest |
| `openFdaApiKey` | string | — | Optional; raises the openFDA page size to 1,000 |
| `maxRecords` | integer | `100` | Per source. `0` for no cap |
| `maxVehicles` / `maxVins` | integer | `25` / `500` | Input caps |
| `incremental` | boolean | `false` | Return only what has changed since the last run |
| `stateStoreName` | string | `product-recalls-api-state` | Where the cursor lives |
| `includeSummary` | boolean | `true` | Free summary rows |
| `maxConcurrency` | integer | `4` | Sources and vehicles in flight |
| `maxRunSecs` | integer | `240` | Wall-clock budget |
| `proxyConfiguration` | object | Apify datacenter | See **Proxy** below |

***

### Output reference

`rowType` is one of `recall`, `complaint`, `rating`, `vin`, `summary` or
`diagnostic`. Every row carries every column; the ones that do not apply are
null, so the dataset exports as one rectangular table however the run was
configured.

**Every row:** `ok`, `rowType`, `input`, `error`, `errorType`, `scrapedAt`,
`source`, `sourceUrl`, plus `agency`, `sourceDataset`, `recordId`, `apiUrl`,
`proxyPath` and `fetchedVia`.

**Recall core:** `recallNumber`, `campaignNumber`, `eventId`, `title`, `summary`,
`description`, `reason`, `date`, `dateRaw`, `lastPublishDate`, `reportDate`,
`initiationDate`, `classificationDate`, `terminationDate`, `closedDate`,
`classification`, `riskLevel`, `recallType`, `status`, `voluntaryMandated`,
`initialFirmNotification`, `isArchived`, `relatedToOutbreak`.

**Product:** `productType`, `categoryId`, `productNames`, `productDescription`,
`productModels`, `productQuantity`, `unitsText`, `units`, `upcs`, `codeInfo`,
`brands`, `genericNames`, `substanceNames`, `productNdcs`, `applicationNumbers`,
`images`, `relatedUrls`.

**Firm and distribution:** `manufacturer`, `manufacturers`, `recallingFirm`,
`importers`, `distributors`, `retailers`, `soldAt`, `establishment`,
`manufacturerCountries`, `firmCity`, `firmState`, `firmCountry`, `postalCode`,
`distributionPattern`, `states`, `consumerContact`, `mediaContact`,
`companyMediaContact`, `qtyRecovered`.

**Hazard and remedy:** `hazard`, `hazards`, `hazardType`, `consequence`,
`remedy`, `remedyOptions`, `component`, `componentRaw`, `components`,
`injuriesText`, `injuryCount`, `deathCount`, `crash`, `fire`.

**Vehicle:** `vehicleKey`, `vehicleMake`, `vehicleModel`, `vehicleModelYear`,
`vehicleDescription`, `parkIt`, `parkOutSide`, `overTheAirUpdate`,
`potentialUnitsAffected`, `odiNumber`, `maskedVin`, `incidentDate`,
`complaintFiledDate`.

**Rating:** `vehicleId`, `overallRating`, `overallRatingStars`, the front, side,
pole and rollover ratings, `rolloverPossibility`, `dynamicTipResult`,
`electronicStabilityControl`, `forwardCollisionWarning`, `laneDepartureWarning`,
`complaintsCount`, `recallsCount`, `investigationCount`, `media`.

**VIN:** `vin`, `vinValid`, `vinErrorCodes`, `vinErrorTexts`, `suggestedVin`,
`vinMake`, `vinModel`, `vinModelYear`, `vinManufacturer`, and the decoded
specification — body, trim, engine, transmission, plant, restraints and driver
assistance — plus `vpicRaw` when you ask for it.

Six dataset views are built in: **Recalls**, **Vehicle recalls**, **Owner
complaints**, **Crash-test ratings**, **Decoded VINs** and **Problems**.

#### Dates

Every date column is ISO `YYYY-MM-DD`, and `dateRaw` keeps whatever the agency
printed. That matters more here than usual: **NHTSA prints recall dates as
`DD/MM/YYYY` and complaint dates as `MM/DD/YYYY`** — the same API, two
conventions. This Actor has two separate parsers and never lets one read the
other's field.

#### Free summary rows

- `summaryType: "source"` — what each agency returned, how many requests it took,
  which date window was asked for, and for USDA whether the text filter or the
  full list was used.
- `summaryType: "vehicle"` — the resolved make, model and year, NHTSA's own
  recall and complaint totals for cross-checking, and the VIN-range limitation.
- `summaryType: "vin-batch"` — one row per `vehicleKey` that your VINs decoded
  to, carrying how many recalls, complaints and ratings that model produced and
  how many duplicate lookups the dedupe saved. Join it to the `vin` rows on
  `vehicleKey`. The counts live here rather than on the `vin` rows themselves
  because a paid VIN row is written the moment it decodes — it is never held back
  waiting on a recall lookup that a deadline or a budget might cut short.
- `summaryType: "run"` — totals for the whole run, including how many records
  were skipped as already seen and how many were deduplicated.

***

### What you are never charged for

- Diagnostic rows of every type: `not-found`, `no-results`, `ambiguous`,
  `invalid-input`, `blocked`, `timeout`, `unavailable`.
- Every summary row.
- Records your filters excluded — the filters run **before** the charge.
- Records already returned by an earlier incremental run.
- Records deduplicated across sources, or across several VINs of the same model.
- **A VIN we cannot decode.** You get a free row saying why, with vPIC's own
  error codes and its suggested correction.
- **A vehicle description that matches more than one model.** You get the list of
  candidates, free, instead of a guess.
- CPSC's placeholder record, in every form it takes.
- A run that returns nothing at all. It finishes **FAILED**, says why, and bills
  nothing — start fee included.

***

### Pricing

| Event | FREE | BRONZE | SILVER | GOLD |
|---|---|---|---|---|
| Run started | $0.001 | $0.001 | $0.001 | $0.001 |
| **Recall or rating returned** | $0.0007 | $0.0007 | $0.00056 | $0.00042 |
| Owner complaint returned | $0.00015 | $0.00015 | $0.00015 | $0.00015 |
| VIN decoded | $0.0005 | $0.0005 | $0.0004 | $0.0003 |

That is **$0.70 per 1,000 recalls** and crash-test ratings, **$0.15 per 1,000**
owner complaints, and **$0.50 per 1,000** VINs decoded. Prices shown are what a
new account pays on the FREE tier, not a best-case GOLD number.

**Complaints are priced separately for a reason, and you should know it before
you switch them on.** NHTSA returns every complaint against a vehicle in a single
response, and popular models have a lot of them: one 2021 Tesla Model 3 comes
back with **660**. That is why `includeComplaints` defaults to **false** and
`maxComplaintsPerVehicle` defaults to **200** — 200 complaints is $0.03, and the
uncapped 660 would be $0.099.

The run honours `ACTOR_MAX_TOTAL_CHARGE_USD`: when the budget runs out it stops
fetching rather than handing over rows it cannot bill for, and says so in the run
status.

***

### Proxy

The default is Apify's **datacenter** proxy, and it is enough for NHTSA, vPIC,
openFDA and USDA — USDA's firewall actually prefers it to an unproxied client.

**CPSC is the exception, and it is handled for you.** In our measurements
`saferproducts.gov` answered every datacenter request with a placeholder record
instead of recalls — six queries, six identical placeholders, 15 to 29 seconds
each — while the same query from an unproxied address returned real recalls in
180 milliseconds. So CPSC requests go out **direct first**, retry twice with
backoff, and fall back to your proxy only if all three fail. Every CPSC row
carries `proxyPath` and `fetchedVia` so you can see which leg answered.

The run paces itself: 250–600 ms between successive requests to one host, at most
two batch requests a second to vPIC, at most 200 requests a run to openFDA (whose
keyless quota is 1,000 a day *per IP*, and a shared exit is everybody's budget),
and exactly one fetch of USDA's list per run.

***

### Running it every day

Set `incremental: true` and schedule it. The Actor keeps each record's id **and
its revision marker** in a named key-value store — CPSC's publish date, FDA's
status, USDA's last-modified date, NHTSA's report-received date and star rating.
So a recall the agency re-publishes comes back, an FDA recall that moves from
`Ongoing` to `Terminated` comes back, and an unchanged one does not. On a quiet
day the run finishes **SUCCEEDED** with "Nothing new" in its status and charges
nothing — a scheduled monitor should not look broken because no one recalled
anything that morning.

The first incremental run returns everything up to your caps and seeds the state.
Give two scheduled jobs different `stateStoreName` values and they will not tread
on each other.

***

### Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with **limited permissions**, uses **pay-per-event** pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The **Integrations** tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

```bash
curl -X POST "https://api.apify.com/v2/acts/insight.solutions~product-recalls-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords":["lithium battery"],"sources":["cpsc","fda"],"maxRecords":50}'
```

```python
## pip install apify-client
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/product-recalls-api").call(run_input={
    "vins": ["1HGCV1F34LA012345", "WBA3A5C51CF256581"],
    "includeRatings": True,
    "includeComplaints": True,
    "maxComplaintsPerVehicle": 50,
})

for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if not row.get("ok"):
        continue
    if row["rowType"] == "vin":
        print("VIN", row["vin"], row["vinModelYear"], row["vinMake"], row["vinModel"], row["trim"], sep=" | ")
    elif row["rowType"] == "recall":
        print("RECALL", row["agency"], row["recallNumber"] or row["campaignNumber"],
              row["date"], row["title"], row["sourceUrl"], sep=" | ")
```

Set `incremental: true` when you want a monitor rather than a backfill: the run
returns only records that are new or that the agency has revised.

***

### FAQ

**Which agencies, and how far back?**
NHTSA recalls and owner complaints cover **1949 to the present and are updated
daily**; NCAP crash-test ratings cover **1990 to the present and are updated
annually**. CPSC has published **more than 8,000 recalls** by the agency's own
2018 count, and the newest recall ID we have seen is 10967. openFDA enforcement
covers drug, device and food recalls. USDA FSIS covers meat, poultry and egg
products.

**Why did my VIN come back as a different model than I expected?**
Because the VIN says so. A VIN's world manufacturer identifier and descriptor
section carry the make and model, and vPIC reads them even when the check digit
fails. If the decode surprises you, check `vinErrorCodes` — code `1` is a bad
check digit, which usually means a typo in a VIN that still decodes to something.

**A recall matched my VIN. Is my car definitely affected?**
Not definitely. NHTSA publishes no public VIN-range endpoint, so recalls are
indexed against the model, not the vehicle. A campaign covering part of a
production run comes back for every VIN of that model. Take the
`campaignNumber` to the manufacturer's own VIN lookup to confirm.

**Why is a CPSC hazard a whole sentence instead of a code?**
Because that is what the agency returns. CPSC's API has `HazardType` and
`HazardTypeID` fields and they are empty on every record we have measured, so
`hazard` is the sentence the agency wrote and `hazardType` is honestly null.
The same goes for injuries: CPSC writes *"There have been 10 reported incidents
of the hair dryer smoking or catching fire. No injuries reported."* — that is
`injuriesText`, and `injuryCount` stays null rather than being parsed out of
prose.

**Why are `brands` and `genericNames` empty on my food recalls?**
Because openFDA populates its `openfda` block on drug enforcement records and
leaves it empty on food and device ones. Those three columns are drug-only in
practice. We would rather say so than ship columns that are silently always null.

**Why does CPSC sometimes return a diagnostic instead of recalls?**
CPSC's recall service is read directly from `saferproducts.gov`. When it is
temporarily unavailable it answers with a **placeholder record rather than an
error** — a valid HTTP 200 with a fake recall in it. We detect that, retry three
times, try the proxy, and if it still fails you get a free diagnostic row with
the service's own message. You never get a fake recall.

**Why is USDA slower than the others?**
Because USDA publishes its entire recall list — about 13 MB — in one response and
offers no recall-date range filter at all. The run fetches it once and filters
locally. When you set keywords it first tries USDA's two documented text
parameters and falls back to the full list if they return nothing, because an
empty answer from that API is indistinguishable from a parameter it ignored.

**Do I need an API key?**
No. Every source here is a keyless public federal API. `openFdaApiKey` is
optional and only raises openFDA's page size from 100 to 1,000.

**Can I use this data?**
These are public federal records. CPSC, NHTSA, FDA and USDA publish no attribution
requirement, rate limit or terms of use for the endpoints used here beyond
NHTSA's note that vPIC applies automated traffic control. Check the agency's own
page if your use is commercial and high volume.

**What happens if a source changes its format?**
Agency APIs change without notice. When a record cannot be read the run returns a
free diagnostic row naming the request rather than a wrong number, and the run
only finishes successfully if it returned at least one real record.

***

### Limitations

- **Vehicle records are matched by model, not by VIN range.** Said again here
  because it is the single most important thing about this dataset.
- **NHTSA defect investigations and the ODI-number lookup are not in this
  version.** Both routes we tried answered `Missing Authentication Token`.
  `investigationCount` on a rating row is the only investigation signal shipped.
- **CPSC hazard-type codes and UPCs are not available.** The agency returns both
  empty on every record we have measured. The `UPC` search parameter is
  correspondingly near-useless.
- **CPSC has no paging and no limit parameter.** The whole result set arrives in
  one body, so `maxRecords` is a client-side cut and a request with no filter at
  all would download all 11,000-odd records. The run will not issue one: with no
  keyword and no dates it bounds CPSC to the last 90 days.
- **USDA's record shape is read from the agency's documentation.** The 13 MB list
  was too large to capture during development. Every field is read defensively,
  so an unexpected shape produces null columns rather than a failure.
- **NCAP ratings have no publication date**, so a `dateFrom`/`dateTo` filter does
  not apply to them. Neither does it apply to VIN rows.
- **`potentialUnitsAffected` costs an extra request per campaign**, because
  NHTSA publishes it only on its campaign-number endpoint. It is null unless you
  set `includeCampaignDetail`.
- **No severity score of our own.** FDA and USDA classes, NHTSA's `parkIt` and
  `parkOutSide` flags and the CPSC hazard sentence are passed through under their
  own names. We do not rank them against each other.
- **No EU, Canadian or Australian recalls.** CPSC sometimes links a Health Canada
  or Transport Canada notice of the same recall; those links are in `relatedUrls`.
- The upstream format may change. See the FAQ.

***

### Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

**Video, audio & social**

- [YouTube Transcript API](https://apify.com/insight.solutions/youtube-transcript-api) — captions as timed segments, text, SRT or VTT, with language fallback and translation.
- [YouTube Comments API](https://apify.com/insight.solutions/youtube-comments-api) — comments and replies with likes, pinned and hearted flags, newest or top sort.
- [YouTube Channel API](https://apify.com/insight.solutions/youtube-channel-api) — a channel's videos, Shorts and live streams, plus YouTube search.
- [Podcast Search, Episodes & Charts API](https://apify.com/insight.solutions/podcast-api) — Apple Podcasts search, charts and full episode feeds.
- [Bluesky Scraper](https://apify.com/insight.solutions/bluesky-scraper) — profiles, posts, followers and follows from the public AT Protocol API.
- [Telegram Channel Scraper](https://apify.com/insight.solutions/telegram-channel-scraper) — posts, views and channel stats from public Telegram channels.
- [Substack Scraper](https://apify.com/insight.solutions/substack-scraper) — posts with full free text, comments and publication profiles.
- [Hacker News API](https://apify.com/insight.solutions/hacker-news-api) — stories, comments, users, front page and a structured "Who is hiring?" parser from the official HN APIs.

**News, documents & the web**

- [Google News Search, Topics & Real Article URLs](https://apify.com/insight.solutions/google-news-api) — news search and topic feeds with the publisher's real URL decoded.
- [Website to Markdown — Content Extractor for LLMs & RAG](https://apify.com/insight.solutions/website-content-extractor) — any site as clean Markdown, text and heading-aware chunks.
- [Internet Archive API](https://apify.com/insight.solutions/internet-archive-api) — archive.org search, item metadata, files and reviews.
- [Wayback Machine Toolkit](https://apify.com/insight.solutions/wayback-toolkit) — archived URL inventories, snapshots and text diffs between dates.
- [Website Technology Detector](https://apify.com/insight.solutions/website-tech-detector) — the tech stack behind any site, with the evidence for each detection.
- [Domain Intelligence API](https://apify.com/insight.solutions/domain-intelligence-api) — DNS, RDAP registration, TLS certificate and HTTP facts in one row per domain.
- [SEO Page Audit](https://apify.com/insight.solutions/seo-page-audit) — sitemap crawl with on-page checks, structured data and broken-link reports.
- [Keyword Suggestions API](https://apify.com/insight.solutions/keyword-suggestions-api) — Google, YouTube, Bing, Amazon and eBay autocomplete with alphabet and question expansions.
- [Website Contact Extractor](https://apify.com/insight.solutions/website-contact-extractor) — emails, phone numbers and social profiles from any list of websites.
- [Web Search Results API](https://apify.com/insight.solutions/web-search-api) — Bing and DuckDuckGo organic results with snippets, no key, no browser.
- [Company Enrichment API](https://apify.com/insight.solutions/company-enrichment-api) — a domain in, a company profile out: firmographics, contacts, tech stack, DNS and hiring signal.

**Business, finance & jobs**

- [Congress & Insider Trades API](https://apify.com/insight.solutions/congress-insider-trades-api) — STOCK Act periodic transaction reports and SEC Form 4 insider trades in one schema.
- [Federal Contracts, Grants & Lobbying API](https://apify.com/insight.solutions/federal-contracts-grants-api) — SAM.gov opportunities, USAspending awards, Grants.gov notices and Senate lobbying filings in one schema.
- [SEC EDGAR API](https://apify.com/insight.solutions/sec-edgar-api) — filings, XBRL financials and full-text search by ticker or CIK.
- [Clinical Trials & FDA API](https://apify.com/insight.solutions/clinical-trials-fda-api) — ClinicalTrials.gov studies plus openFDA recalls, labels, approvals, 510(k)s and adverse-event reports.
- [Y Combinator Companies, Batches & Founders](https://apify.com/insight.solutions/yc-companies-directory) — the YC directory with founders and social links, filterable by batch, industry and hiring status.
- [Career Site Jobs API](https://apify.com/insight.solutions/ats-jobs-api) — jobs straight from Greenhouse, Lever, Ashby, Workable and 10+ other ATS career sites.
- [New Job Postings Monitor](https://apify.com/insight.solutions/job-postings-monitor) — new, closed and changed postings on the career sites you watch.
- [Remote Jobs API](https://apify.com/insight.solutions/remote-jobs-api) — RemoteOK, Remotive, We Work Remotely, Himalayas, Jobicy and more in one schema, deduplicated.
- [Shopify Products API](https://apify.com/insight.solutions/shopify-products-api) — any Shopify store's catalogue, variants, prices and stock signals.
- [Shopify Store Monitor](https://apify.com/insight.solutions/shopify-store-monitor) — price drops, sales, restocks, sell-outs and new products on any Shopify store, one row per change.

**Apps & games**

- [App Store & Google Play Reviews API](https://apify.com/insight.solutions/app-reviews-api) — reviews from both stores with ratings, versions and developer replies.
- [App Store Top Charts & App Search API](https://apify.com/insight.solutions/app-charts-api) — Apple top charts by country and genre, plus app search and details.
- [App Store Keyword Rank Tracker](https://apify.com/insight.solutions/app-store-keyword-rank-tracker) — where any app ranks for any keyword on the App Store and Google Play, with rank changes and ASO suggestions.
- [Steam Reviews API](https://apify.com/insight.solutions/steam-reviews-api) — Steam reviews with playtime, helpfulness and game details.
- [Steam Game Data API](https://apify.com/insight.solutions/steam-store-stats-api) — prices, tags, review scores, live player counts and top charts.

# Actor input Schema

## `sources` (type: `array`):

Which agencies to read. `cpsc` is consumer products from saferproducts.gov; `nhtsa` is vehicle recalls, owner complaints and NCAP crash-test ratings; `fda` is drug, device and food enforcement reports from openFDA; `fsis` is USDA meat, poultry and egg recalls. All four produce the same columns, so a run over all four is one table. Setting `vehicles` or `vins` switches `nhtsa` on whether or not you list it.

## `keywords` (type: `array`):

Only recalls whose title, product name, description, hazard or reason mentions one of these, e.g. `["battery", "lithium"]`. Sent to CPSC as one request per keyword per search field, to openFDA as a `search=` clause and to USDA FSIS as a partial-match text filter, then re-checked on every row before it is written. Rows that came back because you named a vehicle or a VIN are not keyword-filtered — you asked for that car by name.

## `vehicles` (type: `array`):

Vehicles to look up at NHTSA, as `"2020 Honda Civic"` strings or `{"make": "honda", "model": "civic", "year": 2020}` objects. A model year is required — NHTSA indexes recalls by year from 1949, and a yearless make and model would be 78 requests. Every entry goes through NHTSA's own make and model lists, so the row carries the agency's spelling; a description matching more than one model returns the candidates free instead of guessing.

## `maxRecords` (type: `integer`):

How many recall rows to return from each source. The three FDA enforcement endpoints share one budget rather than getting one each. 0 means every record the filters allow. Owner complaints and VIN rows have their own caps and are not counted here.

## `includeRatings` (type: `boolean`):

Fetch NHTSA's New Car Assessment Program ratings for each vehicle: overall, front, side, pole and rollover stars, plus the crash-test photographs and videos. Two requests per vehicle, one row per body-style variant. Not every vehicle is tested; an untested one gets a free diagnostic row.

## `vins` (type: `array`):

VINs to decode with NHTSA's free vPIC decoder, in batches of 50. A partial VIN is written with `*` for the unknown positions. A VIN we cannot decode costs you nothing. Unless `decodeOnly` is on, each decoded VIN is joined to its model's recalls, complaints and ratings — and fifty VINs of one model cost one set of recall rows, not fifty.

## `recallIds` (type: `array`):

Fetch specific records. A CPSC `RecallID` (e.g. `10967`) is looked up at saferproducts.gov; an NHTSA campaign number (e.g. `21V215000`) is recognised by its shape and looked up at NHTSA instead.

## `manufacturers` (type: `array`):

Only recalls whose recalling firm, manufacturer, importer, distributor, retailer or USDA establishment mentions one of these, e.g. `["Peloton"]`. CPSC and openFDA are asked server-side; NHTSA and FSIS are filtered here.

## `hazardKeywords` (type: `array`):

Only recalls whose hazard, consequence or reason mentions one of these, e.g. `["fire", "laceration"]`. CPSC has no hazard-code taxonomy in practice — it returns the hazard as a sentence — so this is a text match on that sentence.

## `remedyKeywords` (type: `array`):

Only recalls whose remedy mentions one of these, e.g. `["refund"]`. CPSC mixes a clean enum (Refund, Replace, Repair) with whole paragraphs of instructions in the same field, so this is a text match rather than a picker.

## `states` (type: `array`):

Two-letter state codes, e.g. `["CA", "TX"]`. Matched against the firm's state and against the distribution text, which is where FSIS and CPSC put it. Vehicle recalls are national and are not affected.

## `classifications` (type: `array`):

Only recalls in these classes. FDA and USDA classify recalls I (reasonable probability of serious harm) to III (unlikely to cause harm); USDA also issues Public Health Alerts. CPSC and NHTSA do not classify recalls and are not affected.

## `dateFrom` (type: `string`):

ISO date, `YYYY-MM-DD`. Sent server-side to CPSC and openFDA; applied here for NHTSA and USDA FSIS, neither of which offers a recall-date range filter. Leave empty and CPSC defaults to the last 90 days — its API has no paging and no limit, so an unbounded request would download all 11,000 records.

## `dateTo` (type: `string`):

ISO date, `YYYY-MM-DD`. Leave empty for today.

## `includeComplaints` (type: `boolean`):

Fetch NHTSA owner complaints for each vehicle. Off by default because complaints arrive in bulk and are billed per complaint: one 2021 Tesla Model 3 returns 660 in a single request. `maxComplaintsPerVehicle` bounds it.

## `maxComplaintsPerVehicle` (type: `integer`):

How many owner complaints to return for one vehicle. NHTSA sends the whole set in one response; this is the cut, and it is what bounds the bill.

## `decodeOnly` (type: `boolean`):

Decode each VIN and stop there — no recalls, complaints or ratings are fetched for the model it decodes to. The cheapest way to use this Actor as a VIN decoder.

## `fdaSearch` (type: `string`):

An openFDA `search=` expression, ANDed with everything else, e.g. `initial_firm_notification:"Press Release"`. For people who know the openFDA query language; everyone else should use the filters above.

## `fsisServerSideFilter` (type: `boolean`):

USDA publishes its whole recall list — about 13 MB — in one response with no date-range filter. When you set keywords, the run first tries USDA's two documented partial-match text parameters, and falls back to the full list if they return nothing, because an empty answer from that API is also what a parameter it ignores looks like. Turn this off to always fetch the full list.

## `includeSummary` (type: `boolean`):

Add free `summary` rows: one per source, one per vehicle (with the resolved make and model, NHTSA's own recall and complaint totals, and the VIN-range limitation) and one for the run. Never charged.

## `vinModelYear` (type: `string`):

A model year to send alongside a partial or short VIN, which is what lets vPIC decode one. Ignored for full 17-character VINs.

## `includeRawVin` (type: `boolean`):

Attach all 154 vPIC fields to each VIN row as `vpicRaw`. The flattened columns cover the ones people use; this is for the rest.

## `maxRatingVariantsPerVehicle` (type: `integer`):

NHTSA rates body styles separately — a 2020 Honda Civic has a 2-door and a 4-door rating. This caps how many of them are fetched.

## `includeCampaignDetail` (type: `boolean`):

Fill `potentialUnitsAffected` on vehicle recalls. NHTSA publishes that number only on its campaign-number endpoint, so this costs one extra request per distinct campaign.

## `maxCampaignLookups` (type: `integer`):

Caps the extra requests `includeCampaignDetail` makes.

## `crashOnly` (type: `boolean`):

Only owner complaints NHTSA records as involving a crash. The flag is on the record itself, not inferred from the text.

## `fireOnly` (type: `boolean`):

Only owner complaints NHTSA records as involving a fire.

## `resolveAmbiguous` (type: `string`):

`"2020 Honda Civ"` matches five NHTSA models. By default that is a free diagnostic row listing all five and nothing is fetched or charged. `first` takes the first candidate; `all` treats every candidate as its own vehicle, each counting against `maxVehicles`.

## `cpscSearchFields` (type: `array`):

Which CPSC fields each keyword is searched against. CPSC ANDs everything it is sent, so an OR across fields is one request per field — which is why this is a list and why `maxCpscRequests` exists. Only CPSC's own documented parameter names are accepted.

## `maxCpscRequests` (type: `integer`):

How many requests one run may make to saferproducts.gov. Keywords times search fields, plus one per manufacturer, hazard, remedy and recall id.

## `openFdaApiKey` (type: `string`):

Your own free openFDA key. Without one the API allows 100 records per page and 1,000 requests a day per IP; with one it allows 1,000 per page and a far larger daily quota. Everything here works without it.

## `maxVehicles` (type: `integer`):

How many vehicles to resolve and fetch for.

## `maxVins` (type: `integer`):

How many VINs to decode. They go out in batches of 50, which is vPIC's documented ceiling.

## `incremental` (type: `boolean`):

Return only records you have not been given before. The run keeps each record's id **and its revision marker** — CPSC's publish date, FDA's status, USDA's last-modified date, NHTSA's report date and star rating — so a recall the agency re-publishes comes back and an unchanged one does not. VIN rows are never suppressed: you asked for that VIN by name.

## `stateStoreName` (type: `string`):

The named key-value store the incremental cursor lives in. Give two scheduled jobs different names and they will not tread on each other.

## `maxConcurrency` (type: `integer`):

How many sources, and how many vehicles within NHTSA, to read at once. Each parallel worker keeps its own proxy session.

## `maxRunSecs` (type: `integer`):

Wall-clock budget for the whole run. When it runs out, the rows already written are kept and a free diagnostic row says where the walk stopped.

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

Apify's datacenter proxy is enough for NHTSA, vPIC, openFDA and USDA FSIS — and USDA's firewall actually prefers it. CPSC is the exception: saferproducts.gov answered every datacenter request with a placeholder record in our measurements, so CPSC requests go out on the run's own address first and fall back to this proxy only if that fails.

## Actor input object example

```json
{
  "sources": [
    "cpsc",
    "fda"
  ],
  "keywords": [
    "battery"
  ],
  "vehicles": [
    "2020 Honda Civic"
  ],
  "maxRecords": 10,
  "includeRatings": true,
  "vins": [],
  "recallIds": [],
  "manufacturers": [],
  "hazardKeywords": [],
  "remedyKeywords": [],
  "states": [],
  "classifications": [],
  "dateFrom": "",
  "dateTo": "",
  "includeComplaints": false,
  "maxComplaintsPerVehicle": 200,
  "decodeOnly": false,
  "fdaSearch": "",
  "fsisServerSideFilter": true,
  "includeSummary": true,
  "vinModelYear": "",
  "includeRawVin": false,
  "maxRatingVariantsPerVehicle": 5,
  "includeCampaignDetail": false,
  "maxCampaignLookups": 25,
  "crashOnly": false,
  "fireOnly": false,
  "resolveAmbiguous": "diagnostic",
  "cpscSearchFields": [
    "RecallTitle",
    "ProductName",
    "RecallDescription"
  ],
  "maxCpscRequests": 12,
  "maxVehicles": 25,
  "maxVins": 500,
  "incremental": false,
  "stateStoreName": "product-recalls-api-state",
  "maxConcurrency": 4,
  "maxRunSecs": 240,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per record from CPSC, NHTSA, openFDA, USDA FSIS and the vPIC VIN decoder, in one schema, plus free per-source, per-vehicle and per-run summary rows. Delivered as JSON items in the default dataset.

# 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 = {
    "sources": [
        "cpsc",
        "fda"
    ],
    "keywords": [
        "battery"
    ],
    "vehicles": [
        "2020 Honda Civic"
    ],
    "maxRecords": 10,
    "includeRatings": true,
    "vins": [],
    "includeComplaints": false,
    "includeSummary": true,
    "incremental": false,
    "maxConcurrency": 4,
    "maxRunSecs": 240,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("insight.solutions/product-recalls-api").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 = {
    "sources": [
        "cpsc",
        "fda",
    ],
    "keywords": ["battery"],
    "vehicles": ["2020 Honda Civic"],
    "maxRecords": 10,
    "includeRatings": True,
    "vins": [],
    "includeComplaints": False,
    "includeSummary": True,
    "incremental": False,
    "maxConcurrency": 4,
    "maxRunSecs": 240,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("insight.solutions/product-recalls-api").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 '{
  "sources": [
    "cpsc",
    "fda"
  ],
  "keywords": [
    "battery"
  ],
  "vehicles": [
    "2020 Honda Civic"
  ],
  "maxRecords": 10,
  "includeRatings": true,
  "vins": [],
  "includeComplaints": false,
  "includeSummary": true,
  "incremental": false,
  "maxConcurrency": 4,
  "maxRunSecs": 240,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call insight.solutions/product-recalls-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,insight.solutions/product-recalls-api"
        }
    }
}
```

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/T5AFgvNAdFxSfifcS/builds/hcZ1Spix2sToiddOS/openapi.json
