Docs preview Need an API key? Compare Free CSV, Free API, Starter, Developer, and Enterprise.

API documentation

Build recall screening into listing workflows.

Use CatalogRecall API to submit resale listings, catalog rows, brands, UPCs, model numbers, and lot codes, then receive confidence-scored recall candidates with source links and review guidance.

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.

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