# California Data Broker Registry Monitor — CPPA Delete Act Delta (`malonestar/ca-data-broker-registry-delta`) Actor

California data broker registry API (CPPA / Delete Act): every registered data broker with 2026 disclosure flags (GenAI, foreign actor, minors, geolocation, biometric), DSAR metrics, first-registered year, new/lapsed/renamed/changed delta and is-this-company-registered verification.

- **URL**: https://apify.com/malonestar/ca-data-broker-registry-delta.md
- **Developed by:** [Kyle Maloney](https://apify.com/malonestar) (community)
- **Categories:** Business, Developer tools, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.50 / 1,000 data broker registry rows

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## California Data Broker Registry Monitor — CPPA Delete Act delta, disclosure flags, registration check

**California data broker registry API.** Reads the California Privacy Protection Agency's official Data Broker Registry export (`cppa.ca.gov/data_broker_registry/registry.csv`, ~600 registrants, 77 columns) and turns it into three things a privacy-compliance or adtech due-diligence team actually uses:

1. **`registry`** — every registered data broker with its **2026 disclosure flags** normalised to true/false/null (collects minors' data, precise geolocation, biometric, reproductive-health, citizenship, union, sexual-orientation, gender-identity, government IDs, account logins; sold or shared to a **GenAI developer**, a **foreign actor**, the federal government, other states, law enforcement; FCRA / GLBA / IIPPA / CMIA / HIPAA carve-outs), the **2024 DSAR metrics** (requests to delete / know / opt out / limit — received, complied, denied, response days) as numbers, and **first-registered year / date** joined from CPPA's 2024 and 2025 exports and the legacy OAG 2020-2023 registry.
2. **`delta`** — only what changed: **new**, **lapsed**, **renamed** (same website domain re-registered under a new name — 35 such pairs between the 2025 and 2026 filings) and **changed** registrants (a disclosure flag flipped, a website / state / city / contact moved), with `changed_fields` before/after. **The first run is a real delta against CPPA's own 2025 export**, not an inventory dump; later runs compare against a named cross-run baseline.
3. **`verify`** — pass `names[]` and get a **three-valued `is_registered`** per company: `true` (exact normalised name or DBA match), `false` (no match and no candidate), `null` (fuzzy candidates — review, with `evidence` scores). A name match is never upgraded to a legal conclusion.

### Who it is for

- **Privacy-compliance vendors and privacy counsel** (DSAR/DROP tooling, CCPA programs): a *new* registrant is a lead with a legal clock; a *lapsed* one is a compliance question; a flag flip on an existing customer is a contract conversation.
- **Adtech / martech due diligence and vendor risk**: "does this partner sell to GenAI developers or foreign actors, does it hold biometric or minors' data, and how many deletion requests did it deny?"
- **Journalists, researchers, policy teams** tracking the Delete Act cohort year over year.
- **Agents / MCP clients** that need "is company X a registered California data broker?" as a tool call.

### The forcing calendar this actor is built around

- **Annual registration is due January 31** (Cal. Civ. Code § 1798.99.82). Every row carries `next_registration_deadline` and `days_to_next_registration_deadline`.
- **DROP (Delete Request and Opt-out Platform):** registered brokers' obligation to process deletion requests within 45 days took effect **2026-08-01** (`drop_deletion_obligation_effective`).
- **Failure to register: $200 per day** administrative fine plus fees (`unregistered_penalty_usd_per_day`; CPPA enforcement advisory, 2025-12-17).

### Example input

```json
{ "mode": "registry", "flags": ["sold_to_genai_developer"], "maxResults": 100 }
```

```json
{ "mode": "delta", "eventTypes": ["new", "renamed"], "maxResults": 200 }
```

```json
{ "mode": "verify", "names": ["Acxiom", "LexisNexis", "Oracle America"], "maxResults": 10 }
```

Filters (`flags`, `nameQuery`, `websiteQuery`, `state`, `country`) apply in `registry` and `delta` mode. `state` accepts `CA` or `California` — CPPA stores both spellings and this actor normalises them. `flags` is an AND across the listed keys.

### What a row means — and what it never claims

- **`true` / `false` / `null` on every flag is a contract.** `true` = the broker filed Yes, `false` = filed No, `null` = the cell is blank (not answered). Counts over flags (`sensitive_data_flag_count`, `sold_or_shared_flag_count`, `regulated_carveout_count`) are `null` when any component is blank — never a smaller number.
- **DSAR metrics: blank is `null`, not 0.** A broker that reported nothing is distinguishable from one that received zero requests. The year the metrics describe is read from CPPA's own column headers (`dsar_metrics_year`, currently 2024), never assumed.
- **Registration history is cross-file, with its basis on the row.** `first_registered_basis` tells you whether the year came from a dated record (OAG `Date Added` 2020-2023, CPPA 2024 `Completion time`), an undated listing (2025 export), or only the current file. `days_since_first_registration` exists only when a real date does. If a prior-year file could not be read this run, its `in_registry_*` field is `null` (not checked) and `history_status` reads `partial` — never a false "was not registered".
- **`is_registered: false` is a checked negative** against the full, gate-verified registry. `null` means there are fuzzy candidates you should look at — the `evidence` array carries up to five with similarity scores.
- **Delta events are classified once per registrant.** A name that disappears while its website domain re-registers under a new name is `renamed` (with `previous_broker_names`), not a `lapsed` + `new` pair that would bill you twice for a non-event. DSAR metrics are deliberately not change-tracked (they change every filing year by definition); disclosure flags, website domain, state, city and contact email are.

### Drift gate — runs before any billable row

Every run downloads the full export and checks it against what this actor was built on (2026-09-26): the **header contract** (77 named columns resolved by normalised-substring rules that survive CPPA's curly apostrophes, non-breaking hyphens and wording edits — a renamed or dropped required column fails the run), a **row-count floor** (≥400 registrants), **row width**, the **closed Yes/No vocabulary** on all 20 flags, **numeric DSAR cells**, a **positive canary** (at least 2 of Acxiom / Experian Information Solutions / LiveRamp present), a **negative control**, plus corroborating checks (name uniqueness, state populated, `Last-Modified` freshness ≤400 days, DSAR year readable). A load-bearing failure ends the run as FAILED with 0 rows and 0 billed and the reason in the status message. A corroborating failure is disclosed as `drift_gate_status: verified_degraded` and the run proceeds. The freshness headers CPPA served (`source_last_modified`, `source_etag`) ride on every row.

### Traps in the source this actor handles for you

- 62 of 603 rows carry **multi-line quoted comment fields** — a naive line split reads ~175 "rows". RFC-4180 parsing throughout; a truncated download (unterminated quote) is refused, never parsed as a short registry.
- UTF-8 **BOM**, **curly apostrophes** and **U+2011 non-breaking hyphens** in the header text; `CA` vs `California` (and 90+ other spellings, foreign regions included) in the state column; blank-vs-0 DSAR cells; the 2025 export has an internal **notes row above its header**.
- Company names change spelling between years ("Adept ID, Inc." → "AdeptID, Inc."); matching keys are case/punctuation/suffix-insensitive and rename detection uses the website domain.

### Pricing

Pay per result: **$0.01 per row** at the FREE tier (paid plans are discounted 20-45%; see the pricing tab). The full registry is ~600 rows ≈ $6.00; the prefilled 100-row run ≈ $1.00; a delta run costs only what changed (a scheduled run on an unchanged registry emits 0 rows and bills nothing); a verify run costs one row per name. A run that cannot answer (registry unreachable, header contract broken, drift) fails and bills nothing.

### Use as an MCP tool

Call it from any MCP client through `https://mcp.apify.com` with `malonestar/ca-data-broker-registry-delta`. The `verify` mode is the tool shape: `{ "mode": "verify", "names": ["<company>"] }` → one row per name with `is_registered` and `evidence`. Billing is identical to a Console run.

### Output fields

Every row carries every field below; `null` means not published by the broker / not applicable to the mode / not checked this run.

| Field | Type | Meaning (null = not published / not checked) |
|---|---|---|
| `broker_name` | string | Registered legal name of the data broker exactly as filed with the CPPA. |
| `dba` | string | Doing-business-as name(s), if the broker filed any. |
| `website` | string | Primary website as filed (scheme and www prefix vary; see website\_domain). |
| `contact_email` | string | Primary privacy/contact email address filed with the registration. |
| `phone` | string | Primary phone number (optional question on the CPPA form). |
| `street_address` | string | Primary street address as filed. |
| `city` | string | City as filed. |
| `state_raw` | string | State/region exactly as filed ("CA" and "California" both occur; foreign regions appear here too). |
| `zip` | string | Postal code as filed. |
| `country` | string | Country as filed (upper-case in the CPPA export, e.g. UNITED STATES). |
| `privacy_rights_url` | string | URL the broker filed for how California consumers exercise their CCPA rights. |
| `collects_minors` | boolean | Broker discloses it collects personal information of minors. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_account_logins` | boolean | Broker collects consumers' account logins or numbers with security codes granting access to third-party accounts. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_government_ids` | boolean | Broker collects consumers' government-issued identification numbers. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_citizenship_data` | boolean | Broker collects consumers' citizenship data, including immigration status. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_union_membership` | boolean | Broker collects consumers' union membership status. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_sexual_orientation` | boolean | Broker collects consumers' sexual orientation status. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_gender_identity` | boolean | Broker collects consumers' gender identity and gender expression data. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_biometric` | boolean | Broker collects consumers' biometric data. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_precise_geolocation` | boolean | Broker collects consumers' precise geolocation. true = filed Yes, false = filed No, null = left blank (not answered). |
| `collects_reproductive_health` | boolean | Broker collects consumers' reproductive health care data. true = filed Yes, false = filed No, null = left blank (not answered). |
| `sold_to_foreign_actor` | boolean | Broker shared or sold consumers' data to a foreign actor in the past year. true = filed Yes, false = filed No, null = left blank (not answered). |
| `sold_to_federal_government` | boolean | Broker shared or sold consumers' data to the federal government in the past year. true = filed Yes, false = filed No, null = left blank (not answered). |
| `sold_to_state_governments` | boolean | Broker shared or sold consumers' data to other state governments in the past year. true = filed Yes, false = filed No, null = left blank (not answered). |
| `sold_to_law_enforcement` | boolean | Broker shared or sold consumers' data to law enforcement in the past year (other than under subpoena/court order). true = filed Yes, false = filed No, null = left blank (not answered). |
| `sold_to_genai_developer` | boolean | Broker shared or sold consumers' data to a developer of a GenAI system or model in the past year. true = filed Yes, false = filed No, null = left blank (not answered). |
| `regulated_fcra` | boolean | Broker or a subsidiary is regulated by the federal Fair Credit Reporting Act (FCRA carve-out claimed). true = filed Yes, false = filed No, null = left blank (not answered). |
| `fcra_pi_types` | string | If the broker claims the FCRA carve-out: free-text description of the types of personal information covered. null when not regulated or left blank. |
| `fcra_products` | string | If the broker claims the FCRA carve-out: free-text description of the specific products or services covered. null when not regulated or left blank. |
| `fcra_pct_activities` | string | If the broker claims the FCRA carve-out: free-text description of the percentage of data activities covered. null when not regulated or left blank. |
| `regulated_glba` | boolean | Broker or a subsidiary is regulated by the Gramm-Leach-Bliley Act (GLBA carve-out claimed). true = filed Yes, false = filed No, null = left blank (not answered). |
| `glba_pi_types` | string | If the broker claims the GLBA carve-out: free-text description of the types of personal information covered. null when not regulated or left blank. |
| `glba_products` | string | If the broker claims the GLBA carve-out: free-text description of the specific products or services covered. null when not regulated or left blank. |
| `glba_pct_activities` | string | If the broker claims the GLBA carve-out: free-text description of the percentage of data activities covered. null when not regulated or left blank. |
| `regulated_iippa` | boolean | Broker or a subsidiary is regulated by the California Insurance Information and Privacy Protection Act. true = filed Yes, false = filed No, null = left blank (not answered). |
| `iippa_pi_types` | string | If the broker claims the IIPPA carve-out: free-text description of the types of personal information covered. null when not regulated or left blank. |
| `iippa_products` | string | If the broker claims the IIPPA carve-out: free-text description of the specific products or services covered. null when not regulated or left blank. |
| `iippa_pct_activities` | string | If the broker claims the IIPPA carve-out: free-text description of the percentage of data activities covered. null when not regulated or left blank. |
| `regulated_cmia` | boolean | Broker or a subsidiary is regulated by the California Confidentiality of Medical Information Act. true = filed Yes, false = filed No, null = left blank (not answered). |
| `cmia_pi_types` | string | If the broker claims the CMIA carve-out: free-text description of the types of personal information covered. null when not regulated or left blank. |
| `cmia_products` | string | If the broker claims the CMIA carve-out: free-text description of the specific products or services covered. null when not regulated or left blank. |
| `cmia_pct_activities` | string | If the broker claims the CMIA carve-out: free-text description of the percentage of data activities covered. null when not regulated or left blank. |
| `regulated_hipaa` | boolean | Broker or a subsidiary is regulated by HIPAA privacy, security and breach-notification rules. true = filed Yes, false = filed No, null = left blank (not answered). |
| `hipaa_pi_types` | string | If the broker claims the HIPAA carve-out: free-text description of the types of personal information covered. null when not regulated or left blank. |
| `hipaa_products` | string | If the broker claims the HIPAA carve-out: free-text description of the specific products or services covered. null when not regulated or left blank. |
| `hipaa_pct_activities` | string | If the broker claims the HIPAA carve-out: free-text description of the percentage of data activities covered. null when not regulated or left blank. |
| `dsar_delete_received` | number | Requests to delete — total requests received in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_delete_complied_whole` | number | Requests to delete — requests complied with in whole in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_delete_complied_part` | number | Requests to delete — requests complied with in part in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_delete_denied` | number | Requests to delete — requests denied in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_delete_days_mean` | number | Requests to delete — mean days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_delete_days_median` | number | Requests to delete — median days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_received` | number | Requests to know what personal information is collected — total requests received in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_complied_whole` | number | Requests to know what personal information is collected — requests complied with in whole in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_complied_part` | number | Requests to know what personal information is collected — requests complied with in part in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_denied` | number | Requests to know what personal information is collected — requests denied in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_days_mean` | number | Requests to know what personal information is collected — mean days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_collected_days_median` | number | Requests to know what personal information is collected — median days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_received` | number | Requests to know what personal information is sold or shared — total requests received in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_complied_whole` | number | Requests to know what personal information is sold or shared — requests complied with in whole in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_complied_part` | number | Requests to know what personal information is sold or shared — requests complied with in part in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_denied` | number | Requests to know what personal information is sold or shared — requests denied in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_days_mean` | number | Requests to know what personal information is sold or shared — mean days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_know_sold_days_median` | number | Requests to know what personal information is sold or shared — median days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_received` | number | Requests to opt out of sale or sharing — total requests received in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_complied_whole` | number | Requests to opt out of sale or sharing — requests complied with in whole in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_complied_part` | number | Requests to opt out of sale or sharing — requests complied with in part in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_denied` | number | Requests to opt out of sale or sharing — requests denied in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_days_mean` | number | Requests to opt out of sale or sharing — mean days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_optout_days_median` | number | Requests to opt out of sale or sharing — median days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_received` | number | Requests to limit use/disclosure of sensitive personal information — total requests received in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_complied_whole` | number | Requests to limit use/disclosure of sensitive personal information — requests complied with in whole in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_complied_part` | number | Requests to limit use/disclosure of sensitive personal information — requests complied with in part in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_denied` | number | Requests to limit use/disclosure of sensitive personal information — requests denied in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_days_mean` | number | Requests to limit use/disclosure of sensitive personal information — mean days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `dsar_limit_days_median` | number | Requests to limit use/disclosure of sensitive personal information — median days to respond substantively in the metrics year (see dsar\_metrics\_year). Number as filed; null when the cell is blank (NOT 0 — blank means not reported). |
| `additional_comments` | string | Free-text "Additional Context or Comments" filed by the broker (may span multiple lines). |
| `state` | string | US state as a 2-letter code normalised from state\_raw ("California" → "CA"). null for non-US regions or unrecognised values — never guessed. |
| `website_domain` | string | Registrable host extracted from website, lower-case, without scheme or www. Used for rename detection in delta mode. |
| `name_key` | string | Normalised name key (lower-case, punctuation and corporate suffixes removed) — the identity used for delta and verify matching. |
| `dba_key` | string | Normalised key of the DBA name, or null. |
| `sensitive_data_flag_count` | number | Count of the 10 collects\_\* flags filed Yes. null if any of the 10 is blank (a count over an unanswered flag would be a wrong number). |
| `any_sensitive_data_collected` | boolean | true if any collects\_\* flag is Yes; false if all 10 are No; null if none is Yes and at least one is blank. |
| `sold_or_shared_flag_count` | number | Count of the 5 sold\_to\_\* flags filed Yes (null if any is blank). |
| `any_sold_or_shared_disclosure` | boolean | true if any sold\_to\_\* flag is Yes; false if all 5 are No; null when undetermined. |
| `regulated_carveout_count` | number | Count of the 5 regulated\_\* carve-out flags filed Yes (null if any is blank). |
| `any_regulated_carveout` | boolean | true if any regulated\_\* flag is Yes; false if all 5 are No; null when undetermined. |
| `flags_true` | array | List of every disclosure flag key filed Yes. |
| `flags_unanswered` | array | List of disclosure flag keys left blank on the filing. |
| `dsar_total_received` | number | Sum of the 5 \*\_received DSAR counts; null if any is blank. |
| `dsar_total_denied` | number | Sum of the 5 \*\_denied DSAR counts; null if any is blank. |
| `dsar_reported` | boolean | true if at least one DSAR received-count is a number on this filing. |
| `first_registered_year` | number | Earliest year this name key appears across the legacy OAG registry (2020-2023, Date Added), the CPPA 2024 and 2025 exports, and the current registry. null when a prior file was unavailable and the name is not in the ones that loaded. |
| `first_registered_date` | string | ISO date of first registration when a prior file publishes one (OAG "Date Added" or CPPA 2024 "Completion time"); null when only the year is known. |
| `first_registered_basis` | string | Which record gives first\_registered\_year: oag\_legacy\_date\_added | oag\_legacy\_listing\_undated | registry2024\_completion\_time | registry2024\_listing | registry2025\_listing | current\_registry\_only | prior\_files\_unavailable. |
| `days_since_first_registration` | number | Days from first\_registered\_date to this run; null when no dated record exists. |
| `registration_years_seen` | array | Every registration year in which this name key appears (from the files that loaded), ascending. |
| `in_oag_legacy_registry` | boolean | Name key present in the legacy OAG 2020-2023 registry file. null = that file could not be read this run (not checked). |
| `in_registry_2024` | boolean | Name key present in the CPPA 2024 export. null = file unavailable this run. |
| `in_registry_2025` | boolean | Name key present in the CPPA 2025 export. null = file unavailable this run. |
| `history_status` | string | complete = all three prior files loaded; partial = some; unavailable = none (all history fields then null). |
| `event_type` | string | delta mode only: new (not in baseline), lapsed (in baseline, absent now, no domain match), renamed (absent name whose website domain re-registered under a new name), changed (tracked field differs). null in registry/verify mode. |
| `previous_broker_names` | array | renamed events: the baseline name(s) sharing this website domain (several when registrations consolidated). |
| `rename_basis` | string | renamed events: how the pair was made (website\_domain). |
| `changed_fields` | array | changed/renamed events: \[{field, before, after}] for every tracked field that differs (website\_domain, state, city, contact\_email, every disclosure flag; DSAR metrics are not tracked because they change every filing year). |
| `change_categories` | array | Distinct categories of changed\_fields: disclosure\_flags | contact | location | website | name. |
| `query_name` | string | verify mode: the name you asked about, verbatim. |
| `is_registered` | boolean | verify mode: true = exact normalised name/DBA match in the current registry; false = no match and no fuzzy candidate ≥0.6; null = fuzzy candidates exist (see evidence) or the query was unusable — review, do not treat as negative. |
| `match_type` | string | verify mode: exact\_name | exact\_dba | fuzzy\_strong\_review (≥0.9) | fuzzy\_review (≥0.6) | none | unusable\_query. |
| `matched_broker_name` | string | verify mode: the registered name matched exactly (null for fuzzy/none). |
| `evidence` | array | verify mode: up to 5 candidates \[{broker\_name, dba, website, score}] — score is bigram Dice similarity on normalised names, 1 = exact. |
| `candidate_count` | number | verify mode: number of candidates in evidence. |
| `run_mode` | string | registry | delta | verify. |
| `source_url` | string | The CPPA registry export this row was read from. |
| `source_last_modified` | string | Last-Modified header CPPA served for registry.csv on this run (freshness disclosure). |
| `source_etag` | string | ETag header served for registry.csv on this run. |
| `source_bytes` | number | UTF-8 bytes of the decoded export text (the 3-byte BOM CPPA serves is excluded). |
| `source_row_count` | number | Registrants parsed from the current export before any filter. |
| `source_column_count` | number | Columns in the current export header (77 on 2026-09-26). |
| `dsar_metrics_year` | number | The calendar year CPPA names in its DSAR metric headers ("…in 2024"); read from the header, never assumed. |
| `drift_gate_status` | string | verified | verified\_degraded (a corroborating probe failed or could not run; disclosed) — a load-bearing failure fails the run and bills nothing. |
| `drift_probes_verified` | array | Probe names that passed this run. |
| `drift_probes_unavailable` | array | Probe names that could not complete (e.g. freshness when Last-Modified is not served). |
| `baseline_basis` | string | delta mode: prior\_run (named KV store) | cppa\_registry2025\_file (first run, seeded from the pinned prior-year export) . null in other modes. |
| `baseline_row_count` | number | delta mode: registrants in the baseline compared against. |
| `baseline_source_last_modified` | string | delta mode: Last-Modified of the file the baseline was built from. |
| `run_legs_requested` | array | Sources this run attempted: registry plus (when includeHistory) oag\_legacy, registry2024, registry2025. |
| `run_legs_ok` | array | Sources that answered. |
| `run_legs_failed` | array | Sources that failed (corroborating only — a failed registry leg fails the run). |
| `run_legs_failed_reason` | string | Why each failed leg failed, or null. |
| `run_complete` | boolean | true when every requested leg answered; false = partial, with history fields null where their file failed. |
| `matched_rows_total` | number | Rows matched before maxResults was applied. |
| `results_truncated` | boolean | true when matched\_rows\_total exceeds maxResults — this row set is a prefix, not the whole answer. |
| `next_registration_deadline` | string | Next annual CPPA data-broker registration deadline (January 31) after this run, ISO date. |
| `days_to_next_registration_deadline` | number | Days from this run to next\_registration\_deadline. |
| `drop_deletion_obligation_effective` | string | Date the Delete Request and Opt-out Platform (DROP) 45-day deletion-processing obligation took effect for registered brokers. |
| `unregistered_penalty_usd_per_day` | number | Statutory administrative fine for failing to register (Cal. Civ. Code 1798.99.82): $200 per day. |
| `monitored_at` | string | ISO timestamp of this run. |

### FAQ

**Is this the official CPPA registry?** It reads CPPA's own published export on every run and discloses the `Last-Modified` header. It is not affiliated with the CPPA.

**Why does `first_registered_year` say 2026 for a company I know registered earlier?** The name key did not match any prior-year file (`first_registered_basis: current_registry_only`). Companies re-register under new legal names; check `renamed` events in delta mode or the `evidence` in verify mode.

**Why is a count `null` instead of 0?** Because a component flag or metric was blank on the filing. Publishing 0 there would assert something the broker never said.

**Can I get only new data brokers each week?** Yes: schedule `{ "mode": "delta", "eventTypes": ["new"] }`. The baseline lives in a named key-value store on your account, so consecutive runs compare correctly.

**How do I check whether a company is a registered data broker in California?** `{ "mode": "verify", "names": ["Company Name"] }`.

# Actor input Schema

## `mode` (type: `string`):

registry = the full CPPA registrant list (filterable) with 2026 disclosure flags, DSAR metrics and registration history. delta = only what changed since your last run: new, lapsed, renamed and changed registrants (the first run compares against CPPA's own 2025 export, so it is a real delta). verify = check names\[] against the registry and get a three-valued is\_registered per name.

## `flags` (type: `array`):

Optional. Keep only registrants that filed Yes on EVERY listed flag. Keys: collects\_minors, collects\_account\_logins, collects\_government\_ids, collects\_citizenship\_data, collects\_union\_membership, collects\_sexual\_orientation, collects\_gender\_identity, collects\_biometric, collects\_precise\_geolocation, collects\_reproductive\_health, sold\_to\_foreign\_actor, sold\_to\_federal\_government, sold\_to\_state\_governments, sold\_to\_law\_enforcement, sold\_to\_genai\_developer, regulated\_fcra, regulated\_glba, regulated\_iippa, regulated\_cmia, regulated\_hipaa. An unknown key fails the run before any download.

## `nameQuery` (type: `string`):

Optional substring match on the registered name or DBA (case, punctuation and Inc/LLC suffixes ignored). Example: "transunion".

## `websiteQuery` (type: `string`):

Optional substring match on the filed website, e.g. "acxiom.com".

## `state` (type: `string`):

Optional. 2-letter code or full name ("CA" and "California" both match the same registrants — CPPA stores both spellings).

## `country` (type: `string`):

Optional exact match on the filed country, as CPPA publishes it in upper case (UNITED STATES, UNITED KINGDOM, CANADA...).

## `eventTypes` (type: `array`):

delta mode only. Optional subset of new, lapsed, renamed, changed. Empty = all four.

## `names` (type: `array`):

verify mode. One entry per company; you get one row per entry with is\_registered true / false / null (null = fuzzy candidates exist, see evidence — review, do not treat as negative). Max 500.

## `includeHistory` (type: `boolean`):

Also read CPPA's 2024 and 2025 exports and the legacy OAG 2020-2023 registry to fill first\_registered\_year / date, registration\_years\_seen and in\_registry\_\* on every row. Three extra small downloads. Default on.

## `maxResults` (type: `integer`):

Hard cap on rows emitted and billed (1-5000). The full registry is ~600 rows; every row carries matched\_rows\_total and results\_truncated so a capped answer is never mistaken for the whole list.

## Actor input object example

```json
{
  "mode": "registry",
  "names": [
    "Acxiom",
    "LexisNexis",
    "Oracle America"
  ],
  "includeHistory": true,
  "maxResults": 100
}
```

# Actor output Schema

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

No description

# 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 = {
    "mode": "registry",
    "names": [
        "Acxiom",
        "LexisNexis",
        "Oracle America"
    ],
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("malonestar/ca-data-broker-registry-delta").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 = {
    "mode": "registry",
    "names": [
        "Acxiom",
        "LexisNexis",
        "Oracle America",
    ],
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("malonestar/ca-data-broker-registry-delta").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 '{
  "mode": "registry",
  "names": [
    "Acxiom",
    "LexisNexis",
    "Oracle America"
  ],
  "maxResults": 100
}' |
apify call malonestar/ca-data-broker-registry-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,malonestar/ca-data-broker-registry-delta"
        }
    }
}
```

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/0zm9FDtzV431qmJsV/builds/4TKVYcccco2vO70z9/openapi.json
