|
| 1 | +<p align="center"><a href="https://apify.com/DevilScrapes/opencorporates-alternative-scraper"><img src="assets/banner.svg" alt="OpenCorporates Alternative — US Business Registry Scraper — Python example" width="100%"></a></p> |
| 2 | + |
| 3 | +# OpenCorporates Alternative API in Python: US state registry lookups into pandas |
| 4 | + |
| 5 | +<p align="center"> |
| 6 | +<a href="https://apify.com/DevilScrapes/opencorporates-alternative-scraper"><img alt="Run on Apify" src="https://img.shields.io/badge/Run%20on-Apify-DC2626?logo=apify&logoColor=white"></a> |
| 7 | +<img alt="Python" src="https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white"> |
| 8 | +<a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-0A0606"></a> |
| 9 | +<a href="https://github.com/devilscrapes/opencorporates-alternative-api-python/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/devilscrapes/opencorporates-alternative-api-python/actions/workflows/ci.yml/badge.svg"></a> |
| 10 | +</p> |
| 11 | + |
| 12 | +This repository is a compact Python client for the **[OpenCorporates Alternative Scraper](https://apify.com/DevilScrapes/opencorporates-alternative-scraper)** on Apify. You hand it a list of company names, and it hands back what the New York, Colorado, Connecticut and Oregon registries have on file for each one: the legal name the state recorded, the filing type, the standing, when the entity was formed, and who its registered agent is. The lookups run on Apify's servers; your side is a short script and a JSON file. It suits analysts who keep vendor or prospect lists in CSV and want a check they can rerun next quarter. |
| 13 | + |
| 14 | +> **Actor:** [OpenCorporates Alternative — US Business Registry Scraper](https://apify.com/DevilScrapes/opencorporates-alternative-scraper) · **Publisher:** [Devil Scrapes](https://apify.com/DevilScrapes) · **Also in JavaScript:** [opencorporates-alternative-api-javascript](https://github.com/devilscrapes/opencorporates-alternative-api-javascript) |
| 15 | +
|
| 16 | +## 🔥 What you get |
| 17 | + |
| 18 | +- **Records from the states themselves**: names are matched against the free open-data datasets that New York, Colorado, Connecticut and Oregon publish, not against a third-party aggregator or a paid search page. |
| 19 | +- **One schema for four registries**: each state names its columns differently; every row comes back with the same keys, so a single parser covers all of them. |
| 20 | +- **Comparable status bucket**: alongside the state's own wording, `status_normalized` puts each entity into `active`, `inactive` or `unknown`, which you can also filter on before rows are written. |
| 21 | +- **Connecticut officers on request**: with `fetchOfficers` on, matched Connecticut rows carry their principals. Home addresses from that dataset are left out on purpose; only business addresses are kept. |
| 22 | +- **A shaky state does not stop the batch**: temporary errors are retried with backoff, and a state dataset that keeps failing is skipped with a warning while the remaining names and states resolve. |
| 23 | +- **Traceable rows**: each record links to the raw state record and to the dataset's landing page, so an auditor can check the source in one click. |
| 24 | + |
| 25 | +## 🎯 Use cases |
| 26 | + |
| 27 | +### Screen a vendor list before procurement signs anything |
| 28 | + |
| 29 | +Export supplier names from your ERP, feed them through `input.json` and read the output into a DataFrame. Names with no registry hit, or with only inactive hits, become your follow-up list. The rest get a formation date and an entity type next to them for the file. |
| 30 | + |
| 31 | +### Clean a prospect spreadsheet ahead of a campaign |
| 32 | + |
| 33 | +Join the rows back to your lead sheet on the `query` column. Companies that the state marks as dissolved or otherwise inactive can be dropped before anyone spends an email on them, and the registered agent column helps spot shell-like filings that share one agent. |
| 34 | + |
| 35 | +### Run a quarterly re-check from cron |
| 36 | + |
| 37 | +Point a cron entry or an Airflow task at `python main.py`, write each run into a dated folder with `--out`, and compare standings between quarters. A company that moves from active to inactive is exactly the change a credit or compliance team wants surfaced. |
| 38 | + |
| 39 | +### Support an investigation with citable records |
| 40 | + |
| 41 | +Reporters and researchers can collect formation dates and agent names for a list of entities, then cite `source_record_url` for every claim. The link points at the state's own record, which is what an editor or a lawyer will ask for. |
| 42 | + |
| 43 | +## ⚡ Quick start |
| 44 | + |
| 45 | +You need an Apify account and an API token from [Console → Settings → Integrations](https://console.apify.com/settings/integrations). Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below. |
| 46 | + |
| 47 | +```bash |
| 48 | +git clone https://github.com/devilscrapes/opencorporates-alternative-api-python.git |
| 49 | +cd opencorporates-alternative-api-python |
| 50 | +python -m venv .venv && source .venv/bin/activate |
| 51 | +pip install -r requirements.txt |
| 52 | +export APIFY_TOKEN=your_token_here |
| 53 | +python main.py # uses input.json |
| 54 | +python main.py my-input.json --out data --max-charge 2 |
| 55 | +``` |
| 56 | + |
| 57 | +The script starts the [OpenCorporates Alternative — US Business Registry Scraper](https://apify.com/DevilScrapes/opencorporates-alternative-scraper), waits for it to finish, prints a link to the run in Apify Console and saves every result to `results.json` and `results.csv`. It exits with code 1 and the run's status message if the run fails. |
| 58 | + |
| 59 | +## 🧩 The core call |
| 60 | + |
| 61 | +Strip away the CLI handling and this is what remains. The official `apify-client` package starts a run, blocks until it completes, and iterates over the dataset rows for you. |
| 62 | + |
| 63 | +```python |
| 64 | +from apify_client import ApifyClient |
| 65 | + |
| 66 | +client = ApifyClient("YOUR_APIFY_TOKEN") |
| 67 | +run = client.actor("DevilScrapes/opencorporates-alternative-scraper").call(run_input={'searchQueries': ['Blue Sky Consulting LLC'], 'jurisdictions': ['NY', 'CO'], 'fetchOfficers': True, 'maxResultsPerQuery': 5, 'statusFilter': 'any', 'proxyConfiguration': {'useApifyProxy': False}}) |
| 68 | +for item in client.dataset(run.default_dataset_id).iterate_items(): |
| 69 | + print(item) |
| 70 | +``` |
| 71 | + |
| 72 | +## 📥 Input |
| 73 | + |
| 74 | +`main.py` forwards `input.json` to the Actor as written. The sample looks up one common consulting-firm name in the New York and Colorado registries and keeps at most five matches, the same input the Actor's own release check runs, so a first run returns rows. Swap in your own list; `searchQueries` takes up to 500 names in a single run. |
| 75 | + |
| 76 | +```json |
| 77 | +{ |
| 78 | + "searchQueries": [ |
| 79 | + "Blue Sky Consulting LLC" |
| 80 | + ], |
| 81 | + "jurisdictions": [ |
| 82 | + "NY", |
| 83 | + "CO" |
| 84 | + ], |
| 85 | + "fetchOfficers": true, |
| 86 | + "maxResultsPerQuery": 5, |
| 87 | + "statusFilter": "any", |
| 88 | + "proxyConfiguration": { |
| 89 | + "useApifyProxy": false |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +| Field | Type | Required | Default | Allowed values | |
| 95 | +|---|---|---|---|---| |
| 96 | +| `searchQueries` | array | yes | `["Blue Sky Consulting LLC"]` | — | |
| 97 | +| `jurisdictions` | array | no | `["NY", "CO", "CT", "OR"]` | — | |
| 98 | +| `fetchOfficers` | boolean | no | `true` | — | |
| 99 | +| `maxResultsPerQuery` | integer | no | `5` | — | |
| 100 | +| `statusFilter` | string | no | `"any"` | `any`, `active`, `inactive` | |
| 101 | +| `proxyConfiguration` | object | no | `{"useApifyProxy": false}` | — | |
| 102 | + |
| 103 | +## 📤 Output |
| 104 | + |
| 105 | +Each row is one entity that one state returned for one of your names, so a single input name can yield several rows across states. Dates arrive as ISO `YYYY-MM-DD` strings, and nullable columns stay `None` when a state does not publish that value rather than being filled with guesses. |
| 106 | + |
| 107 | +| Field | Type | Always present | |
| 108 | +|---|---|---| |
| 109 | +| `query` | string | yes | |
| 110 | +| `entity_id` | string | yes | |
| 111 | +| `entity_name` | string | yes | |
| 112 | +| `jurisdiction` | string | yes | |
| 113 | +| `entity_type` | string or null | no | |
| 114 | +| `status` | string or null | no | |
| 115 | +| `status_normalized` | string | yes | |
| 116 | +| `formation_date` | string or null | no | |
| 117 | +| `dissolution_date` | string or null | no | |
| 118 | +| `principal_address` | string or null | no | |
| 119 | +| `registered_agent_name` | string or null | no | |
| 120 | +| `registered_agent_address` | string or null | no | |
| 121 | +| `officers` | array | yes | |
| 122 | +| `source_record_url` | string | yes | |
| 123 | +| `registry_url` | string | yes | |
| 124 | +| `scraped_at` | string | yes | |
| 125 | + |
| 126 | +## 💰 Pricing |
| 127 | + |
| 128 | +| Event | Price (USD) | |
| 129 | +|---|---| |
| 130 | +| Actor start | $0.05 | |
| 131 | +| Result row emitted | $0.005 | |
| 132 | + |
| 133 | +Prices as configured on 2026-08-12. The [Actor page](https://apify.com/DevilScrapes/opencorporates-alternative-scraper) always shows the current price. Runs are billed to the Apify account that owns the token. Use `--max-charge` to cap what a single run can cost. |
| 134 | + |
| 135 | +## 🛠️ Working with the data in Python |
| 136 | + |
| 137 | +The snippet below, which needs pandas (`pip install pandas`), rolls the rows up to one line per input name: how many entities matched, in which states, whether any of them is active, and the earliest formation date. It also lists the names from `input.json` that found nothing at all. |
| 138 | + |
| 139 | +```python |
| 140 | +import json |
| 141 | + |
| 142 | +import pandas as pd |
| 143 | + |
| 144 | +names = json.load(open("input.json", encoding="utf-8"))["searchQueries"] |
| 145 | +rows = pd.DataFrame(json.load(open("results.json", encoding="utf-8"))) |
| 146 | + |
| 147 | +rows["formed"] = pd.to_datetime(rows["formation_date"], errors="coerce") |
| 148 | +rows["is_active"] = rows["status_normalized"].eq("active") |
| 149 | + |
| 150 | +summary = ( |
| 151 | + rows.groupby("query") |
| 152 | + .agg( |
| 153 | + matches=("entity_id", "size"), |
| 154 | + states=("jurisdiction", lambda s: ",".join(sorted(set(s)))), |
| 155 | + any_active=("is_active", "any"), |
| 156 | + earliest_formed=("formed", "min"), |
| 157 | + ) |
| 158 | + .reset_index() |
| 159 | +) |
| 160 | +summary.to_csv("verification_summary.csv", index=False) |
| 161 | +print(summary.to_string(index=False)) |
| 162 | + |
| 163 | +not_found = sorted(set(names) - set(rows["query"])) |
| 164 | +print("No registry match:", ", ".join(not_found) or "none") |
| 165 | +``` |
| 166 | + |
| 167 | +## ❓ FAQ |
| 168 | + |
| 169 | +### Does this use OpenCorporates data or need an OpenCorporates key? |
| 170 | + |
| 171 | +Neither. The Actor reads the four states' own open-data datasets, so the only credential you need is an Apify API token. It is an alternative source for the same kind of answer, not a wrapper around OpenCorporates. |
| 172 | + |
| 173 | +### Which states are covered? |
| 174 | + |
| 175 | +New York, Colorado, Connecticut and Oregon. Narrow the list with `jurisdictions` when you know where a company is registered; fewer states means fewer requests and a quicker run. |
| 176 | + |
| 177 | +### Why is `status` empty for some New York or Oregon rows? |
| 178 | + |
| 179 | +Those two states do not include a standing column in their open datasets. The Actor keeps `status` as `None` instead of inventing one, and its `status_normalized` value for those states is a default, not a verified standing. Treat an `active` filter on NY and OR rows as a weak signal and confirm important cases with the state. |
| 180 | + |
| 181 | +### Why is the `officers` list empty? |
| 182 | + |
| 183 | +Only Connecticut publishes a principals dataset that the Actor reads, so rows from the other three states always carry an empty list. For Connecticut, keep `fetchOfficers` set to `true`. |
| 184 | + |
| 185 | +### How fresh is the data? |
| 186 | + |
| 187 | +As fresh as each state's published snapshot. New York describes its dataset as current to the last business day of the month, so recent filings can lag there. None of the four sources is a live transactional lookup. |
| 188 | + |
| 189 | +## 🧯 Troubleshooting |
| 190 | + |
| 191 | +- **`Set APIFY_TOKEN first`**: export the token in the same shell, or put it in a local `.env` file your shell loads. Never commit it. |
| 192 | +- **Run finished with `FAILED` or `ABORTED`**: open the printed Console link; the run log says why. Input validation errors name the field. |
| 193 | +- **Fewer rows than expected**: a `--max-charge` cap also limits how many results can be charged. Raise the cap or lower the limits in your input. |
| 194 | +- **Something looks wrong in the data**: open an issue on the [Actor page](https://apify.com/DevilScrapes/opencorporates-alternative-scraper) so the maintainers see it. |
| 195 | + |
| 196 | +## 🔗 More from Devil Scrapes |
| 197 | + |
| 198 | +- [OpenCorporates Alternative — US Business Registry Scraper on Apify Store](https://apify.com/DevilScrapes/opencorporates-alternative-scraper) |
| 199 | +- [JavaScript version of this example](https://github.com/devilscrapes/opencorporates-alternative-api-javascript) |
| 200 | +- [All Devil Scrapes Actors](https://apify.com/DevilScrapes) |
| 201 | +- [Apify web scraping cookbook](https://github.com/devilscrapes/apify-web-scraping-cookbook) |
| 202 | + |
| 203 | +Related Actors: |
| 204 | + |
| 205 | +- [New Business Filings — US State Registry Leads Feed](https://apify.com/DevilScrapes/new-business-filings-leads-scraper) — a dated feed of newly formed companies from the same four state registries. |
| 206 | +- [UCC Lien Filing Scraper — CT + CO State Portals](https://apify.com/DevilScrapes/ucc-lien-filing-scraper) — lien filings against debtors in Connecticut and Colorado. |
| 207 | +- [GLEIF LEI Scraper — Legal Entity Identifier Records](https://apify.com/DevilScrapes/gleif-lei-records-scraper) — Legal Entity Identifier records when the counterparty sits outside these states. |
| 208 | + |
| 209 | +--- |
| 210 | + |
| 211 | +MIT licensed example code. The Actor itself runs on [Apify](https://apify.com/DevilScrapes/opencorporates-alternative-scraper) and is maintained by [Devil Scrapes](https://apify.com/DevilScrapes). Not affiliated with OpenCorporates or any US state Secretary of State office. |
0 commit comments