Self-hosted CLI that automates CCPA/CPRA data-broker opt-out requests via Playwright
Project description
OptOut
Self-hosted CLI that automates CCPA/CPRA data-broker opt-out requests.
Why this exists
CCPA and CPRA give California residents the right to demand deletion of their data from any company that sells it. Data brokers — Whitepages, BeenVerified, Spokeo, and dozens more — are legally required to comply. They also make exercising that right as hostile as possible: a different form on every site, CAPTCHAs, phone verification calls, 45-day deadlines you have to track manually, and data that silently reappears 60–90 days later.
OptOut automates the hostile parts. You run it on your own machine with your own information. There is no central server, no account, no SaaS subscription — which means the "authorized agent" legal complexity that DeleteMe and Optery have to navigate simply does not apply.
Install
pipx install optout
playwright install chromium # one-time browser download (~130 MB)
optout init # interactive setup wizard
Requirements: Python 3.12+, pipx, Playwright
Quickstart
optout init # create ~/.config/optout/config.yml
optout queue # add all brokers to the submission queue
optout submit # open Chrome, walk each broker's form
optout status # table of deadlines and statuses
optout monitor # re-scan; re-queues brokers that re-added you
optout submit opens a real Chrome window for each broker. It fills every form field it can, then pauses with a prompt when human action is needed (CAPTCHA, listing selection, phone verification).
Supported brokers
| Broker | Method | Automation | Human steps | Last verified |
|---|---|---|---|---|
| BeenVerified | Web form | Partial | Click listing; solve Turnstile CAPTCHA | 2026-05-10 |
| MyLife | Web form | Partial | Solve reCAPTCHA; click Submit | 2026-05-10 |
| Radaris | Web form | Mostly automated | Click listing; click "Start Removing" | 2026-05-10 |
| Spokeo | Web form | Partial | Click listing; confirm email | 2026-05-10 |
| Whitepages | Web form | Partial | Click listing; answer phone call | 2026-05-10 |
All five brokers are verified end-to-end as of 2026-05-10. New brokers are added as YAML files — no Python required. See CONTRIBUTING.md.
How it works
OptOut reads broker definitions from YAML files bundled with the package. Each YAML declares the broker's opt-out URL, required fields, and a list of steps. The engine walks those steps using a persistent Playwright Chromium context, so Cloudflare Turnstile cookies survive between runs and you only solve those challenges once.
flowchart LR
CLI["optout CLI\nTyper + Rich"]
DB[("SQLite\nsubmissions\ndeadlines")]
Registry["Broker Registry\nYAML → Pydantic"]
Dispatcher["Dispatcher"]
WebForm["web_form\nPlaywright engine"]
Email["email\nSMTP handler"]
Chrome(["Chromium\npersistent profile"])
Brokers(["Live broker\nwebsites"])
CLI --> Registry
CLI --> DB
CLI --> Dispatcher
Dispatcher --> WebForm
Dispatcher --> Email
WebForm --> Chrome
Chrome --> Brokers
Submissions are tracked in a local SQLite database. optout status surfaces anything overdue. optout monitor re-scans and re-queues automatically — safe to run from cron.
Commands
optout init # interactive setup wizard
optout doctor # check that everything is installed correctly
optout brokers list [--category] # list all known brokers
optout brokers info <slug> # details + your submission history
optout scan [--broker SLUG] # check which brokers list you (no submissions made)
optout queue [--broker SLUG] # add brokers to the submission queue
optout submit [--broker SLUG] [-v] # process the queue; -v shows debug output
optout status [--broker] [--status] # table of submissions and deadlines
optout monitor # one-shot re-scan (cron-friendly)
optout verify [--broker SLUG] # check whether broker form selectors still work
Configuration
optout init writes ~/.config/optout/config.yml (mode 600). Key sections:
profile:
legal_name: "Jane Q Public"
dob: "1990-05-14"
current_address:
street: "123 Main St"
city: "Austin"
state: "TX"
zip: "78701"
emails:
current: ["jane@example.com"]
phones:
current: ["+15125551234"]
email:
method: smtp
smtp:
host: smtp.gmail.com
port: 587
username: jane@example.com
password_env: OPTOUT_SMTP_PASSWORD # read from env, never stored in file
playwright:
headless: false # keep false so you can solve CAPTCHAs
slow_mo_ms: 0
Runtime data lives at:
| Path | Contents |
|---|---|
~/.config/optout/config.yml |
Your profile and settings |
~/.config/optout/browser_profile/ |
Persistent Chrome profile (keeps Cloudflare cookies warm) |
~/.local/share/optout/optout.db |
SQLite — submissions, events, scan history |
~/.local/share/optout/artifacts/ |
Confirmation screenshots |
Adding a broker
New brokers are a single YAML file — no Python needed. Five steps:
1. Verify the opt-out flow manually in a real browser. Note every field, selector, and verification step.
2. Copy the nearest template:
cp src/optout/data/brokers/radaris.yml src/optout/data/brokers/new-broker.yml
3. Fill in the metadata — slug, name, domain, method, opt_out_url, legal_basis, statutory_response_days.
4. Write the steps — each step is a type (navigate, form_fill, click, wait_for, prompt_user_if_present, capture) with the relevant CSS selectors.
5. Validate:
uv run pytest tests/test_production_brokers.py -v
uv run optout verify --broker new-broker --headed
Full step reference and template variables are in CONTRIBUTING.md.
Honest limitations
- Removal is not guaranteed. Some brokers comply in days; others take the full 45-day window or ignore requests.
- Data reappears. 60–90 days is typical. Run
optout monitormonthly to catch this. - CAPTCHAs require a human. The tool pauses and prompts — no CAPTCHA solving, by design.
- Broker forms change. When a broker redesigns their opt-out page, the YAML selectors need updating.
optout verifycatches this;optout verify --headedshows you exactly what changed. - Phone verification is manual. Whitepages calls your phone; you enter the code. The tool waits.
Legal basis
OptOut cites the applicable statute in every submission:
- CCPA §1798.105 — right to deletion, 45-day response window
- CPRA — extends CCPA with stronger enforcement
- GDPR Art. 17 — right to erasure, 30-day response window
Each broker's YAML declares which statutes apply. See COMPLIANCE.md for the full legal posture.
License
AGPL-3.0-only. See LICENSE.
This license was chosen intentionally: anyone who forks this and runs it as a hosted service must publish their changes. The project's legal model depends on each user running their own copy.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file optout-0.2.0.tar.gz.
File metadata
- Download URL: optout-0.2.0.tar.gz
- Upload date:
- Size: 70.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff1e47eef880ff34c6b7b42f9051f304858cb7269563ff2ec7bc61670e6dac51
|
|
| MD5 |
5456f54a5749b4223784a19bca0a9df0
|
|
| BLAKE2b-256 |
f8a7252d81f2cee8cbf4cc8f91990d75e285754aba13323f099cfeaa962ec53f
|
Provenance
The following attestation bundles were made for optout-0.2.0.tar.gz:
Publisher:
release.yml on Blake104/OptOut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optout-0.2.0.tar.gz -
Subject digest:
ff1e47eef880ff34c6b7b42f9051f304858cb7269563ff2ec7bc61670e6dac51 - Sigstore transparency entry: 1521733445
- Sigstore integration time:
-
Permalink:
Blake104/OptOut@08b65a7c1fe9d1695ef3f92e3d2021ca36200f81 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Blake104
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@08b65a7c1fe9d1695ef3f92e3d2021ca36200f81 -
Trigger Event:
push
-
Statement type:
File details
Details for the file optout-0.2.0-py3-none-any.whl.
File metadata
- Download URL: optout-0.2.0-py3-none-any.whl
- Upload date:
- Size: 58.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d45aae27e505b95261e70f0b86ad47f572120a839e311c86c655fd1ddc783ed
|
|
| MD5 |
8b37683c1fa6fd38eff7ba65b225d014
|
|
| BLAKE2b-256 |
e82bd41c42a734ccd518c6c003a14b44f46421b8c64f5f6fc2f26f7db97110e5
|
Provenance
The following attestation bundles were made for optout-0.2.0-py3-none-any.whl:
Publisher:
release.yml on Blake104/OptOut
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optout-0.2.0-py3-none-any.whl -
Subject digest:
7d45aae27e505b95261e70f0b86ad47f572120a839e311c86c655fd1ddc783ed - Sigstore transparency entry: 1521733534
- Sigstore integration time:
-
Permalink:
Blake104/OptOut@08b65a7c1fe9d1695ef3f92e3d2021ca36200f81 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Blake104
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@08b65a7c1fe9d1695ef3f92e3d2021ca36200f81 -
Trigger Event:
push
-
Statement type: