API docs
Every U.S. recall from CPSC, FDA, USDA FSIS and NHTSA, in one JSON format. Base URL: https://recallrelay.com
Quickstart
Create a free key on the home page, then:
curl "https://recallrelay.com/v1/recalls?q=crib&min_severity=high&limit=5" \ -H "X-API-Key: $RECALLRELAY_KEY"
Lists come back as {"data": [...], "total": 128, "limit": 5, "offset": 0, "next": "…"}. Follow next to page.
Your API key
Send it in the X-API-Key header, or as Authorization: Bearer <key>. Keep it on your server; don't ship it in a browser or mobile app. /v1/stats works without a key.
Limits and errors
| Plan | Requests | Alerts | Items per catalog check |
|---|---|---|---|
| Free | 100 a day | 1 | 25 |
| Starter | 10,000 a month | 5 | 500 |
| Pro | 100,000 a month | 50 | 5,000 |
| Business | 1,000,000 a month | 500 | 50,000 |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A catalog check costs one request per 100 items.
Errors use HTTP status codes and one shape:
{"error": {"code": "limit_reached", "message": "Your Free plan limit is used up. It resets 2026-11-15."}}
| 400 invalid_request | A parameter is wrong; the message says which and how to fix it. |
| 401 missing_key | No key, or the key isn't active. |
| 404 not_found | No recall, alert or endpoint with that id. |
| 429 limit_reached | Plan limit used up until the reset date. |
| 502 upstream_unavailable | NHTSA's live vehicle lookup didn't answer. Retry shortly. |
The recall record
Every agency's recalls use these fields. Agency-only details (lot codes, injuries, do-not-drive flags) live in extra.
| Field | What it holds |
|---|---|
id | Stable id: `<source>-<agency recall number>` |
source | One of: cpsc, fda, fsis, nhtsa. |
agency | |
agency_name | |
category | One of: consumer_product, food, drug, device, meat_poultry_egg, vehicle, vehicle_equipment, tire, child_seat. |
title | |
description | What was recalled. |
hazard | Why it was recalled. |
remedy | What owners should do. |
classification | Agency class (Class I, II, III) when the agency assigns one. |
severity | One of: low, medium, high. |
severity_basis | `estimated` means Recall Relay inferred severity from the hazard text. One of: agency_class, agency_advisory, estimated. |
status | One of: ongoing, completed, terminated. |
recall_date | |
published_date | |
companies | |
products | |
upcs | Barcodes named in the recall: labeled UPC, GTIN, EAN or (for medical devices) UDI-DI, check digit verified. |
states | Two-letter codes; `US` means nationwide. |
distribution | |
units_affected | |
url | The agency's own record. |
images | |
extra | Agency-specific fields (lot codes, injuries, do-not-drive flags...). |
first_seen | When Recall Relay first saw it. |
Verifying alerts
Alert deliveries are POSTed as JSON with a RecallRelay-Signature header: t=<unix time>,v1=<hex>. The hex is an HMAC-SHA256 of "<t>.<raw body>" using the secret you got when you created the alert. Reply with any 2xx status; anything else is retried with backoff.
Python
import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
mac = hmac.new(secret.encode(), f'{parts["t"]}.'.encode() + raw_body, hashlib.sha256)
return hmac.compare_digest(mac.hexdigest(), parts["v1"])
Node.js
import crypto from "node:crypto";
export function verify(rawBody, header, secret) {
const p = Object.fromEntries(header.split(",").map(s => s.split("=")));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const mac = crypto.createHmac("sha256", secret).update(`${p.t}.${rawBody}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(p.v1));
}
/v1/recallsSearch recalls
Full-text search across every agency with filters. Newest first, or `sort=relevance`.
| Parameter | Description |
|---|---|
q |
Words to search for. Every word must match; the last word matches as a prefix. Example: crib |
source |
Comma-separated agencies. Example: cpsc,fda |
category |
Comma-separated categories: consumer_product, food, drug, device, meat_poultry_egg, vehicle, vehicle_equipment, tire, child_seat. Example: food,drug |
min_severity |
Only recalls at or above this severity. One of: low, medium, high. |
since |
Recall date on or after (YYYY-MM-DD). Example: 2026-01-01 |
until |
Recall date on or before (YYYY-MM-DD). |
state |
Two-letter state; nationwide recalls always match. Example: CA |
company |
Company name contains this text. Example: medtronic |
status |
ongoing, completed or terminated. One of: ongoing, completed, terminated. |
sort |
recent (default) or relevance. One of: recent, relevance. |
limit |
1 to 100. Default 20. |
offset |
For paging. |
curl "https://recallrelay.com/v1/recalls?q=crib&source=cpsc,fda&category=food,drug" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/recalls/{id}Get one recall
| Parameter | Description |
|---|---|
id required |
Recall id, for example `fda-D-0006-2027`. |
curl "https://recallrelay.com/v1/recalls/fda-D-0006-2027" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/upc/{upc}Look up a barcode
Recalls that name this UPC, EAN or GTIN. Leading zeros and spaces are ignored.
| Parameter | Description |
|---|---|
upc required |
The barcode digits. Example: 867166000004 |
curl "https://recallrelay.com/v1/upc/867166000004" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/checkCheck a product list
Send your catalog; get back which items match a recall. Barcodes give exact matches; brand and name give possible matches to review. Costs 1 request per 100 items.
curl -X POST "https://recallrelay.com/v1/check" \
-H "X-API-Key: $RECALLRELAY_KEY" -H "Content-Type: application/json" \
-d '{"items": [{"id": "sku-1001", "upc": "8 67166 00000 4"}, {"brand": "Sharper Image", "id": "sku-1002", "name": "Tabletop Fire Pit, concrete"}]}'
/v1/vehiclesRecalls for a vehicle
Recalls for one make, model and model year, straight from NHTSA (cached 24 hours).
| Parameter | Description |
|---|---|
make required |
Vehicle make. Example: honda |
model required |
Vehicle model. Example: pilot |
year required |
Model year. Example: 2023 |
curl "https://recallrelay.com/v1/vehicles?make=honda&model=pilot&year=2023" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/watchesList alerts
curl "https://recallrelay.com/v1/watches" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/watchesCreate an alert
Get a signed webhook the moment a new recall matches. Set any of `q`, `upcs`, `companies` (a recall matches if any of them match), and narrow with `categories`, `sources`, `min_severity` and `states`. Each list takes up to 200 values; create more alerts for longer lists. The webhook_url must be a public https address. The response includes the signing secret once.
curl -X POST "https://recallrelay.com/v1/watches" \
-H "X-API-Key: $RECALLRELAY_KEY" -H "Content-Type: application/json" \
-d '{"categories": ["consumer_product"], "min_severity": "medium", "name": "Baby gear we resell", "q": "crib", "webhook_url": "https://example.com/hooks/recalls"}'
/v1/watches/{id}Delete an alert
| Parameter | Description |
|---|---|
id required |
Alert id. |
/v1/watches/{id}/testSend a test alert
| Parameter | Description |
|---|---|
id required |
Alert id. |
/v1/statsNo keyCoverage and freshness
Record counts and the last successful update for each agency. No key needed.
curl "https://recallrelay.com/v1/stats" \ -H "X-API-Key: $RECALLRELAY_KEY"
/v1/meYour plan and usage
curl "https://recallrelay.com/v1/me" \ -H "X-API-Key: $RECALLRELAY_KEY"