Base path
/v1
Auth
Bearer API key
Primary route
POST /recalls/match
Quickstart
Make your first recall match request.
Copy these commands into a terminal, replace the key value, then
run a live match. Send productName at minimum; brand,
UPC, model, category, lot code, and manufacture date make the
result easier to trust.
export CATALOGRECALL_API_URL="https://api.catalogrecallapi.com"
export CATALOGRECALL_API_KEY="cr_live_REPLACE_WITH_YOUR_KEY"
curl "$CATALOGRECALL_API_URL/v1/recalls/match" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"productName": "Jif peanut butter 16 oz",
"brand": "Jif",
"category": "food",
"upc": "051500255162",
"lotCode": "1274425",
"liveCheck": true
}'
The API returns JSON only. A non-empty matches array
means your system should show a review step, source link, and
recommended action before a human makes a safety decision.
Local setup
Store your base URL and key before calling endpoints.
Use environment variables in backend services, cron jobs, and
server-side integration tests. Do not put production keys in
frontend JavaScript, mobile app bundles, URLs, logs, or
screenshots.
# macOS, Linux, or server shell
export CATALOGRECALL_API_URL="https://api.catalogrecallapi.com"
export CATALOGRECALL_API_KEY="cr_live_REPLACE_WITH_YOUR_KEY"
# Optional unauthenticated health check
curl "$CATALOGRECALL_API_URL/health"
# .env example for a Node, Rails, Django, or Laravel backend
CATALOGRECALL_API_URL=https://api.catalogrecallapi.com
CATALOGRECALL_API_KEY=cr_live_REPLACE_WITH_YOUR_KEY
Integration checklist
Wire the API into a review workflow, not a hard blocker.
CatalogRecall API is most useful when your app records the input
you sent, the response status, the top candidate, and the human
review decision. This gives your support and compliance teams a
clear audit trail when a listing is flagged later.
- 1. Collect fields
-
Send product title first, then enrich with brand, category,
model, UPC, SKU, lot code, and manufacture date when known.
- 2. Call backend
-
Keep API keys server-side. Your frontend should call your own
backend, and your backend should call CatalogRecall API.
- 3. Route result
-
Continue normal workflow for low-risk rows, send possible and
likely matches to review, and ask for lot/date details when
required.
- 4. Preserve evidence
-
Store status, confidence, matched fields, source URL,
detailPath, and reviewer notes with your listing or catalog
item.
- 5. Recheck later
-
Use recent recalls and source freshness for recurring scans,
nightly jobs, or manual rescreening of active inventory.
Authentication
Every recall and usage endpoint uses bearer auth.
Send the raw key exactly once in the
Authorization header. Raw keys are shown once after
checkout, so store yours in a secrets manager or server
environment immediately.
Authorization: Bearer cr_live_REPLACE_WITH_YOUR_KEY
curl -i "$CATALOGRECALL_API_URL/v1/usage" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
Best inputs
More identifiers produce cleaner recall decisions.
The matcher is built for messy catalog rows, but the best request
looks like a normalized product record. Use the title customers
see, then add every identifier your system already stores.
- productName
-
Required. Listing title, product title, or catalog name.
Maximum 200 characters.
- brand
-
Recommended. Helps separate similar products from different
makers. Maximum 120 characters.
- category
-
Recommended when known, such as food, baby gear, appliance,
toy, medical device, or furniture.
- upc
-
Optional barcode, UPC, EAN, or GTIN. Maximum 32 characters.
- model
-
Optional model number or variant. Especially useful for CPSC
and device recalls.
- lotCode
-
Optional lot, batch, best-by, date, or production code.
Especially useful for food recalls.
- sku
-
Optional seller SKU. Useful when SKU text contains model,
size, lot, or variant information.
- manufactureDate
-
Optional date or date-code text. Keep the original label if
that is all you have.
- liveCheck
-
Optional boolean. When true, food inputs can use a live
openFDA fallback after the local database finds no match.
POST /v1/recalls/match
Check one product, listing, or catalog row.
The match endpoint normalizes submitted fields, searches connected
recall records, scores candidates, and returns up to five
review-worthy matches with field-level reasons.
curl "$CATALOGRECALL_API_URL/v1/recalls/match" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"productName": "Graco FastAction Fold stroller",
"brand": "Graco",
"category": "baby gear",
"model": "FastAction Fold",
"upc": "",
"sku": "GRACO-FASTACTION-FOLD",
"lotCode": "",
"manufactureDate": "",
"liveCheck": false
}'
// Node 22+ backend example
const response = await fetch(`${process.env.CATALOGRECALL_API_URL}/v1/recalls/match`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CATALOGRECALL_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
productName: "Jif peanut butter 16 oz",
brand: "Jif",
category: "food",
upc: "051500255162",
lotCode: "1274425",
liveCheck: true
})
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.error?.message ?? "Recall match failed");
}
# Python backend example
import os
import requests
response = requests.post(
f"{os.environ['CATALOGRECALL_API_URL']}/v1/recalls/match",
headers={
"Authorization": f"Bearer {os.environ['CATALOGRECALL_API_KEY']}",
"Content-Type": "application/json",
},
json={
"productName": "Jif peanut butter 16 oz",
"brand": "Jif",
"category": "food",
"upc": "051500255162",
"lotCode": "1274425",
"liveCheck": True,
},
timeout=15,
)
response.raise_for_status()
result = response.json()
{
"status": "needs_lot_verification",
"topConfidence": 0.82,
"matches": [
{
"source": "openFDA",
"sourceRecordId": "F-1234-2026",
"title": "Example peanut butter recall",
"productSummary": "Creamy peanut butter, 16 oz jars",
"hazardOrReason": "Potential salmonella contamination",
"classification": "Class I",
"recallDate": "2026-04-15",
"reportDate": "2026-04-16",
"matchedFields": ["product_name", "brand", "upc"],
"matchConfidence": 0.82,
"scoreBreakdown": {
"brand": {
"score": 0.25,
"reason": "Brand appeared in the recall text."
}
},
"requiresLotVerification": true,
"recommendedAction": "verify_lot_or_date_code",
"sourceUrl": "https://api.fda.gov/food/enforcement.json",
"detailPath": "/v1/recalls/F-1234-2026"
}
],
"sourceChecks": [
{
"source": "catalogrecall_database",
"status": "checked",
"candidateCount": 2
}
],
"warning": "This is an automated recall match, not an official determination. Verify against the official recall notice before taking action."
}
POST /v1/recalls/bulk-csv
Run recall matching across a small CSV batch.
Send CSV text from your backend when you want to screen a queue of
listings or catalog rows. The first row must be headers. Each
request accepts up to 100 product rows and 256 KB of CSV text.
- csv
-
Required string. Header row plus product rows. Maximum 256 KB.
- liveCheck
-
Optional boolean. Applies live openFDA food fallback to every
parsed row.
- maxRows
- Optional integer. Must be 1-100. Defaults to 100.
curl "$CATALOGRECALL_API_URL/v1/recalls/bulk-csv" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"csv": "productName,brand,category,upc,lotCode\nJif peanut butter 16 oz,Jif,food,051500255162,1274425\nGraco FastAction Fold stroller,Graco,baby gear,,",
"liveCheck": true,
"maxRows": 100
}'
// Node backend: turn uploaded rows into a CSV string before posting
const csv = [
"productName,brand,category,upc,lotCode",
"Jif peanut butter 16 oz,Jif,food,051500255162,1274425",
"Graco FastAction Fold stroller,Graco,baby gear,,"
].join("\n");
const response = await fetch(`${process.env.CATALOGRECALL_API_URL}/v1/recalls/bulk-csv`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CATALOGRECALL_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ csv, liveCheck: true, maxRows: 100 })
});
Accepted product-name headers include productName,
product_name, title,
listing_title, item_name, and
name. Common aliases also map
manufacturer to brand, barcode or
gtin to UPC, and batch_code to lot code.
Unsupported columns are ignored and returned as warnings.
Search and recent recalls
Explore connected recall records.
Use search for support tools and internal lookup screens. Use
recent recalls to monitor newly imported records. Use detail paths
from match, search, or recent responses to fetch the full
normalized recall record.
curl "$CATALOGRECALL_API_URL/v1/recalls/search?query=peanut+butter" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/recalls/search?q=stroller" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/recalls/recent?limit=25" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/recalls/recent?source=openFDA%20Device&limit=25" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/recalls/RECALL_ID_OR_SOURCE_RECORD_ID" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
- query or q
- Required for search. String up to 200 characters.
- source
-
Optional for recent. Exact source name, such as openFDA,
openFDA Device, or CPSC.
- limit
-
Optional for recent. Integer from 1-100. Defaults to 25.
Source freshness
Show which datasets were checked.
Source summaries expose connected datasets, sync time, freshness
state, and coverage notes. Show this context in admin dashboards,
review queues, and incident runbooks when teams ask whether a
dataset is current.
curl "$CATALOGRECALL_API_URL/v1/recalls/sources" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
Usage
Track API activity by billing period.
Usage returns monthly calls used, remaining included calls, plan
context, billing-period dates, and event breakdowns for the
authenticated customer. Poll it from an internal admin screen or
alert when remaining calls get low.
curl "$CATALOGRECALL_API_URL/v1/usage" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
{
"plan": {
"name": "developer",
"includedApiCalls": 15000
},
"apiCalls": {
"used": 42,
"remaining": 14958
},
"usage": {
"recall_check": 40,
"recall_search": 2
}
}
Subscription
Show plan details and open Stripe billing.
Billing routes use the same bearer API key as recall endpoints.
Use the subscription route to show the customer their current
plan. Use the portal route when they need invoices, payment
method changes, cancellation, or an upgrade from Starter to
Developer.
curl "$CATALOGRECALL_API_URL/v1/billing/subscription" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/billing/portal-session" \
-X POST \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
{
"plan": {
"name": "starter",
"includedApiCalls": 2500,
"upgradeAvailable": true,
"manageableInPortal": true
},
"stripe": {
"customerId": "cus_...",
"subscriptionId": "sub_..."
}
}
Rate limits and retries
Use headers to back off cleanly.
Authenticated routes include rate-limit headers. If you receive a
429 rate_limit_exceeded response, wait until the
reset timestamp before retrying. For CSV batches, retry the full
request only when your own workflow can tolerate duplicate review
events.
- X-RateLimit-Limit
- Allowed requests for this API key in the current one-minute window.
- X-RateLimit-Remaining
- Requests remaining before this key is temporarily rate limited.
- X-RateLimit-Reset
- Unix timestamp, in seconds, when the current window resets.
let response = await fetch(url, options);
if (response.status === 429) {
const resetAt = Number(response.headers.get("X-RateLimit-Reset")) * 1000;
const waitMs = Math.max(resetAt - Date.now(), 1000);
await new Promise((resolve) => setTimeout(resolve, waitMs));
response = await fetch(url, options);
}
API keys
List keys and revoke old credentials from your backend.
API key management endpoints are authenticated with an active key.
You cannot revoke the same key that is authenticating the current
request, so confirm another active key is available before
revoking an old key.
curl "$CATALOGRECALL_API_URL/v1/api-keys" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
curl "$CATALOGRECALL_API_URL/v1/api-keys/API_KEY_ID/revoke" \
-X POST \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY"
How matching works
Use the response as a review signal, not a final verdict.
CatalogRecall API normalizes your input, builds search terms,
retrieves candidate recall records, scores token, brand, model,
UPC, lot-code, category, and package-size overlap, then returns a
status and recommended action. When liveCheck is true
and the local database finds no match, food inputs can also
trigger a live openFDA fallback.
- status
- Top-level review state for the request.
- topConfidence
- Highest candidate confidence, from 0 to 1.
- matches
-
Review-worthy candidates with source identifiers, source URLs,
score breakdowns, and actions.
- sourceChecks
-
Which local or live sources were checked and how many
candidates they produced.
- recommendedAction
-
Per-match next step, such as continue normal workflow, manual
review, verify lot/date code, or verify official notice.
- warning
-
Safety disclaimer to preserve in review tools and
customer-facing workflows.
function routeRecallResult(result) {
if (result.status === "likely_match") {
return "hold_listing_until_official_notice_review";
}
if (result.status === "possible_match") {
return "send_to_manual_review_queue";
}
if (result.status === "needs_lot_verification") {
return "request_lot_or_date_code_before_decision";
}
if (result.status === "source_unavailable") {
return "retry_or_verify_directly_with_source";
}
return "continue_standard_listing_flow";
}
Response interpretation
Status values are review guidance, not final safety decisions.
- no_match
-
No matching recall was found in connected sources. Do not
present this as a guarantee.
- weak_match
-
A low-confidence candidate was found. Review only if product
details overlap in your workflow.
- possible_match
-
A meaningful candidate was found and should be reviewed.
- likely_match
-
A strong candidate was found and should be verified against
the official source notice.
- needs_lot_verification
-
Product details match, but lot, date, or production-code
information is needed.
- source_unavailable
-
A live source could not be checked. Retry later and verify
directly with official sources if risk is high.
Hold
Use for likely_match. Keep the item out of the
normal flow until a reviewer checks the source notice.
Review
Use for possible_match and
needs_lot_verification. Ask for model, lot, date,
or source confirmation.
Continue with caution
Use for no_match. Continue normal workflow only
with the warning that no match is not a safety guarantee.
const ACTION_BY_STATUS = {
likely_match: "hold_for_manual_review",
possible_match: "send_to_manual_review",
needs_lot_verification: "request_lot_or_date_details",
weak_match: "optional_review_if_high_risk_category",
source_unavailable: "retry_or_review_if_high_risk_category",
no_match: "continue_normal_workflow_with_disclaimer"
};
Safety language
Never present a no-match result as a safety guarantee.
CatalogRecall API is an automated matching layer. Results may be
incomplete, delayed, ambiguous, or affected by missing source
data. Always verify possible matches against official source
notices before removing, blocking, selling, refunding, or
communicating about a product.
Suggested UI copy:
No recall match was found in the connected sources checked by CatalogRecall API.
This is not a safety guarantee. Continue to follow applicable recall, resale,
and product-safety requirements.
Troubleshooting
Common and uncommon errors use one envelope.
{
"error": {
"code": "invalid_product_input",
"message": "productName is required.",
"status": 400,
"details": {
"fields": ["unexpectedField"]
}
}
}
- 401 missing_api_key
-
Add
Authorization: Bearer YOUR_KEY. Do not use
query-string keys.
- 401 invalid_api_key
-
Check for copied spaces, revoked keys, inactive accounts, or
using a placeholder key.
- 400 invalid_product_input
-
Send a JSON object with
productName. Remove
unsupported fields and use strings for text values.
- 400 invalid_csv_input
-
Include a header row and at least one product row. Keep
maxRows between 1 and 100.
- 400 invalid_json
-
Validate quotes, commas, and escaping. Also send
Content-Type: application/json.
- 400 missing_query
-
Add
?query=... or ?q=... to search
requests.
- 403 cors_origin_not_allowed
-
Your browser origin is not allowlisted. Call the API from your
backend or add the site origin in deployment config.
- 404 recall_not_found
-
Check the recall ID or source record ID. Detail paths should
come from match, search, or recent responses.
- 408 request_timeout
-
Retry with fewer CSV rows, disable live fallback, or try again
after checking deployment health.
- 413 payload_too_large
-
CSV body exceeds 256 KB or the request body is too large.
Split the batch.
- 429 rate_limit_exceeded
-
Back off until
X-RateLimit-Reset. Use response
headers to schedule retries.
- 500 internal_error
-
Retry once, then contact support with timestamp, route, status
code, and request ID if available.
- 503 setup_required
-
The deployment is missing required server configuration. Check
API environment variables.
- source_unavailable
-
The match request succeeded, but a live source failed. Treat
it as incomplete coverage and verify manually.
# See status and rate-limit headers while debugging
curl -i "$CATALOGRECALL_API_URL/v1/recalls/match" \
-H "Authorization: Bearer $CATALOGRECALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"productName":"Jif peanut butter 16 oz","brand":"Jif","liveCheck":true}'
Get an API key