DEV Community

Devil Scrapes
Devil Scrapes

Posted on

Our two-state UCC run returned 200 rows, 127 filings, and zero from Colorado

Quick answer

Before the UCC Lien Filing Scraper ever went public, a deep cloud run asked it for Connecticut and Colorado filings from the last 120 days, capped at 200 rows. The run came back SUCCEEDED with 200 rows. Every one of them was from Connecticut. Colorado — which had real filings in that exact window — contributed zero. And those 200 rows described only 127 distinct filings.

Both numbers were telling us something. One was a bug. The other was the data being honest about its own shape, and the fix there was to our copy, not our code.

How does a two-state run return rows from only one state?

By handing the whole budget to whoever asks first. The first version of the run loop gave the entire remaining maxResults to the first state it fetched. Connecticut went first, had well over 200 matching records in 120 days, and spent the whole cap before Colorado was ever queried.

Nothing failed. There was no error to log. The run did exactly what it was told, and the result looked like "Colorado had nothing this quarter" — which is a plausible-sounding lie. We only caught it because we checked Colorado's portal directly for the same window and it returned real filings. Running the Connecticut fetch alone with a budget of 200 then reproduced the exact 200-rows / 127-filings result, which pinned it on the cap, not on a broken Colorado adapter.

The fix: maxResults is now split evenly across the states you request, up front, and any share a state doesn't use rolls over to the next one so the cap is never wasted. The same failing input now returns 100 Connecticut rows and 100 Colorado rows. Our post-fix cloud QA runs (daysBack: 14, maxResults: 25) landed 13 CT and 12 CO rows each time.

Why were 200 rows only 127 filings?

Because a UCC filing can name more than one debtor, and Connecticut publishes one record per debtor per filing. We checked filing 0005384782 against data.ct.gov by hand: four genuine source records, same filing number, four different debtors. In that same run, one filing — 0005384859 — accounted for 43 rows on its own.

Colorado was the side out of step. Its adapter joined each filing to its debtor dataset and kept only the first debtor it saw, silently discarding the rest. So the two states disagreed on what a row even meant.

We had a choice: collapse Connecticut into one row per filing with a debtor list, or expand Colorado to one row per debtor. We expanded Colorado. Connecticut's native shape is per-debtor, so aggregating would mean inventing a structure the source doesn't publish. And the people who buy this data — lenders, collections shops, lead-gen teams — want each debtor as its own row with its own address, ready to import as a contact.

That decision is visible on your bill, so we put it next to the price rather than in a footnote: a row is a filing–debtor pair. A filing with three debtors is three rows and three result events. Rows that share a filing_id are the same lien.

Why does Colorado need a three-way join?

Connecticut ships one flat dataset. Colorado splits the same information across three linked Socrata datasets — a filings index, a debtor table, and a secured-party table — joined on fileid. The filings index alone held 2,594,585 rows when we measured it, so an unbounded pull is never acceptable. Every query is date-bounded server-side (daysBack, or dateFrom/dateTo) and paged.

Here is a real Colorado row from a cloud run, after the join:

{
  "state": "CO",
  "filing_id": "2594901",
  "filing_date": "2026-09-08T00:00:00.000",
  "lapse_date": "2031-09-08T00:00:00.000",
  "filing_type": "ucc",
  "filing_description": "UCC financing statement",
  "transaction_type": "original",
  "debtor_address": "5045 BEACH CT",
  "debtor_city": "DENVER",
  "secured_party_name": "GoodLeap, LLC",
  "secured_party_state": "CA",
  "status": "active"
}
Enter fullscreen mode Exit fullscreen mode

Some Colorado filings join to nothing — a termination amendment we sampled (2595634) had no debtor or secured-party rows at all. Those still ship, with the missing fields set to null, instead of one empty join sinking the whole run.

What the Actor gives you

One normalized row per filing–debtor pair across Connecticut and Colorado in a single run: state, filing_id, filing_date, lapse_date, filing_type, transaction_type / is_amendment, debtor name and address, secured party name and address, a derived status (active, lapsed, terminated, unknown), and a source_record_url that queries the state portal for that exact filing. You can filter by debtor name with debtorNameContains. There is no API key and no login. We handle the pacing, retries with backoff on 408 / 429 / 5xx, and the join between the two states' schemas, so what you get is one dataset rather than two government schemas to reconcile.

Honest limitations 🚧

  • Two states only. Connecticut and Colorado. It is not a national UCC search. If a debtor only filed in a third state, you correctly get zero rows.
  • Colorado individual debtors come back without a name. Colorado stores business debtors in organizationname and people in separate firstname / lastname fields, and v1 reads only the first. In our two post-fix QA samples, 6 of 12 and 8 of 12 Colorado rows had an empty debtor_name. The address is still there and business debtors are unaffected. We found this while writing this post and it's the next fix on the list.
  • Rows are not sorted newest-first. In both 14-day QA runs, every row was dated the first day of the window. If maxResults is smaller than the window, you get the oldest filings in it. To catch fresh filings, shrink daysBack; don't just lower the cap.
  • Dates are Socrata timestamps (2026-09-08T00:00:00.000), not bare YYYY-MM-DD.
  • UCC filings only. Colorado's IRS and hospital lien types are out of scope, and so are scanned UCC-1 images and collateral text.

FAQ

Do I need an API key or a state portal login?
No. Both states publish this as public, keyless open data.

What does it cost?
$5.20 per 1,000 rows under Pay-Per-Event: a $0.20 start fee plus $0.005 per row written to your dataset. A row is a filing–debtor pair, so budget by debtors, not filings.

Can I monitor new liens against a specific business?
Yes. Set debtorNameContains and a short daysBack, then run it on a schedule. A new financing statement against a business means it just took on secured credit.

What if my search matches nothing?
The run succeeds with zero rows and a status message saying what was searched. An empty result is still an answer.

→ UCC Lien Filing Scraper on Apify


Built by Devil Scrapes. We handle the pacing, retries and cross-state schema joins, and when a run returns something that looks plausible, we check it against the source before we believe it.

Top comments (0)