California Prop 65 60-Day Notice & Settlement Feed avatar

California Prop 65 60-Day Notice & Settlement Feed

Pricing

from $20.00 / 1,000 results

Go to Apify Store
California Prop 65 60-Day Notice & Settlement Feed

California Prop 65 60-Day Notice & Settlement Feed

Feed of California Proposition 65 60-day notices of violation from the Attorney General's register: new and changed records only, with alleged violators, chemicals, product, filer, the civil penalty and attorneys' fees of each attached settlement or judgment, and links to the filed PDFs.

Pricing

from $20.00 / 1,000 results

Rating

0.0

(0)

Developer

Minty Modesty

Minty Modesty

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Categories

Share

California Prop 65 60-Day Notice Feed

Every Proposition 65 enforcement action in California starts the same way: a private enforcer serves a 60-day notice of violation on a business and files a copy with the Attorney General. The Attorney General publishes those filings, and later attaches the settlements, judgments, complaints and withdrawals that follow from them.

The register is searchable one page at a time and its CSV export stops at 1,000 rows. This Actor turns it into a feed: give it a date window and optional filters, and it returns the notices and case documents that are new or changed since your last run, as structured records with working links to the filed PDFs.

If you sell into California, defend Prop 65 claims, underwrite product liability, or track a serial filer, the useful question is not "what is in the register" but "what appeared since Tuesday". That is what comes out of this.

What one record looks like

{
"agNumber": "2026-03559",
"noticeDate": "2026-07-24",
"noticingParty": "Blue Sky Forever",
"plaintiffAttorney": "Seven Hills LLP",
"allegedViolators": ["Pereg Natural Foods Inc."],
"allegedViolatorsRaw": "Pereg Natural Foods Inc.",
"chemicals": ["Lead"],
"chemicalsRaw": "Lead",
"source": "Quinoa Spinach",
"caseId": null,
"caseType": null,
"civilPenaltyUsd": null,
"attorneyFeesUsd": null,
"otherPaymentsUsd": null,
"injunctiveRelief": null,
"noticeDetailUrl": "https://oag.ca.gov/prop65/60-Day-Notice-2026-03559",
"noticePdfUrl": "https://oag.ca.gov/system/files/prop65/notices/2026-03559.pdf",
"changeType": "new",
"recordKey": "2026-03559|",
"scrapedAt": "2026-07-26T03:02:51.273Z"
}

And once a settlement is filed against that notice, the same feed hands you a second record for it, marked changeType: "new", carrying the money:

{
"agNumber": "2022-00526",
"caseId": "Settlement 1427",
"caseType": "settlement",
"caseName": "EHA v. Global Healing Center, LLC, et al.",
"courtDocketNumber": "22CV010847",
"civilPenaltyUsd": 5000,
"attorneyFeesUsd": 45000,
"otherPaymentsUsd": 0,
"injunctiveRelief": "reformulation or warnings",
"caseDocumentUrl": "https://oag.ca.gov/system/files/prop65/settlements/2022-00526S1427.pdf"
}

Input

FieldWhat it does
since / untilFiling-date window, YYYY-MM-DD or MM/DD/YYYY. Defaults to the last 30 days. The register goes back to 1988.
chemicalsListed chemicals, spelled as in the Attorney General's Chemical dropdown (Lead, Cadmium, Bisphenol A (BPA), Di(2-ethylhexyl)phthalate (DEHP), …). Several are combined with OR. A misspelled name fails the run instead of quietly returning everything.
productKeywordsSubstring match on the notice's Source/Product text (powder, socks, vinyl gloves).
defendantsAlleged violator name (Amazon, Target).
plaintiffNoticing party or its attorney, for following one filer.
includeSettlementsInclude the settlement / judgment / complaint / withdrawal rows attached to each notice. On by default.
onlyNewSinceLastRunReturn only new and changed records. On by default.
resolveDetailFieldsRead the notice page as well, for the split violator list, split chemical list and working PDF link. On by default.
maxItemsCap on records written. 0 for no cap.
resetBaselineForget what previous runs saw, so the window comes back in full. Use it for a first backfill.
stateStoreNameNamed key-value store holding the fingerprints. Separate watch lists want separate names.

A daily monitor of lead-in-food notices looks like this:

{
"since": "2026-07-01",
"chemicals": ["Lead"],
"productKeywords": ["powder", "supplement"],
"includeSettlements": true,
"onlyNewSinceLastRun": true
}

Schedule it, point a webhook at the dataset, and each run charges you for the handful of records that actually appeared. A quiet day costs one Actor start and nothing else.

How the change detection works

The register's export has one row per notice per attached case document. A single notice can carry seven rows once its complaint, settlements and judgments are all on file, so the notice number by itself does not identify a row. The key used here is (AG Number, Case ID), which was checked against a 566-row export covering January and February 2022: 414 distinct notice numbers, 566 distinct keys, no collisions.

Each run fingerprints all 17 source columns of every row and compares them against the fingerprints stored in your key-value store:

  • a (AG Number, Case ID) pair never seen before → changeType: "new";
  • a pair seen before whose columns now differ → changeType: "changed", with the previous fingerprint attached. This is how an amended notice reaches you, and how a settlement filed months after the notice reaches you;
  • everything else is skipped.

Raw responses are written to the same store keyed by request URL before anything is parsed. Windows that ended in the past are served from that copy on later runs; a window that reaches today is always refetched, because it can still gain rows. So a backfill you have already paid for is never fetched twice, and re-running with different filters reads what is already on disk.

The export truncates at 1,000 rows and has no page 2 (attach=page_2 returns the same rows as attach=page_1). A window that comes back exactly full is therefore bisected by date and each half fetched separately, so a multi-year backfill returns complete data.

Field notes

The 17 source columns are passed through under camelCase names. Where a column needed interpretation, here is what it was checked against.

Money columns. Civil Penalty, Attorney Fees and Other Payments are the three payment lines on form JUS 1501, Report of Settlement, which private enforcers file with the Attorney General under Health and Safety Code section 25249.7(e) and (f). On the form they read PAYMENT: CIVIL PENALTY, PAYMENT: ATTORNEYS FEES and PAYMENT: OTHER. Filers type bare dollar amounts, so 5000 means five thousand dollars. Each column is delivered twice: civilPenaltyRaw is the string as filed, civilPenaltyUsd is the number, and the number is null rather than a guess whenever the string is not plain digits. The form does not define which payments belong under PAYMENT: OTHER, so no meaning beyond the form's own label is claimed for otherPaymentsUsd.

Which rows carry which columns. Checked row by row across the same 566-row export:

Row typePopulated
Notice with no case document yet (181 rows)notice fields only
Complaint N (99 rows)typeOfClaim, reliefSought, caseName, courtDocketNumber — 99/99
Settlement N (202 rows)the three payment columns and injunctiveRelief — 202/202
Judgment N (35 rows)the three payment columns and injunctiveRelief — 35/35
Withdrawal N (49 rows)notice fields only

No row of any other type carried a payment figure, and no settlement or judgment row was missing one.

Violator and chemical lists. The export joins these with ", ", and the values themselves contain commas: Jo-Ann Stores, LLC is one company and 1,1,1,2-Tetrachloroethane is one chemical. Splitting the export string on commas corrupts both. So allegedViolatorsRaw and chemicalsRaw are always the string as published, and the allegedViolators / chemicals arrays come from the notice page, where the Attorney General publishes each value in its own element. With resolveDetailFields off, the arrays are omitted and detailsResolved is false, rather than a comma split being passed off as a list.

PDF links. noticePdfUrl cannot be assembled from the notice number: some notices are stored under a revised filename and the plain one returns 403. Compare notices/2026-03559.pdf (200) with notices/2026-03540.pdf (403) and notices/2026-03540_0.pdf (200). With resolveDetailFields on, the link is read off the notice page, and noticeDocumentUrls lists every document filed against that notice. Case-document links follow the folder pattern settlements/…S…, complaints/…C…, judgments/…J…, withdrawals/…W….

Comments. The Attorney General's CMS appends its own field placeholder, [field_prop65_comments], to every populated comment. It is stripped.

Source and terms

Data comes from the Attorney General's public 60-day notice register at oag.ca.gov/prop65, which serves both the search pages and the CSV export without a login. No rate limiting was observed while building this, and requests are still made one at a time rather than in parallel. Fetched records are public filings about businesses, not personal data. Naming a business in a notice is an allegation by a private enforcer, not a finding by any court or agency, and the Attorney General publishes the filings without endorsing them; treat the records the same way.

This Actor is not affiliated with the California Department of Justice or the Office of the Attorney General, and nothing it returns is legal advice.

Pricing

Pay per result: the charge is per record written to the dataset, plus Apify's standard per-run Actor-start event. With onlyNewSinceLastRun left on, a monitoring schedule therefore costs whatever the register actually produced that day, and a quiet day costs just the start event. A historical backfill is priced the same way; cap it with maxItems if you want a hard ceiling. The exact figures are on the pricing tab of this page.

Support

Open an issue on the Actor's Apify page. Include the notice number and the input you ran, which is usually enough to reproduce the problem without a follow-up question. If the Attorney General changes the export's columns, the Actor fails loudly with the column names it received rather than shifting your values one field sideways.