WA Contractor License Scraper (L&I Status Lookup)
Pricing
from $1.60 / 1,000 licence delivereds
WA Contractor License Scraper (L&I Status Lookup)
Washington State L&I contractor license database: JSON records with number, business name (registered entities), type, class, specialty, status, dates, city, UBI. Filter by number or status. Lookup, bulk export or incremental runs. For contractor vetting and compliance. No personal contact fields.
Pricing
from $1.60 / 1,000 licence delivereds
Rating
0.0
(0)
Developer
Steadfetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 hours ago
Last modified
Categories
Share
Tested daily. Last verified: 2026-10-30
Look up contractor, liquor and professional licence records from official US state open-data sources. Built by Steadfetch: web data that does not break.
What it does
The Actor reads three keyless public Socrata datasets and returns one JSON item per row in a unified multi-state schema:
- Washington contractor licences - the L&I Contractor License Data - General dataset (
data.wa.gov, resourcem8qx-ubtq), unchanged since v1. - New York liquor licences - the Current Liquor Authority Active Licenses dataset (
data.ny.gov, resource9s3h-dpkz, about 60,000 active licences, refreshed daily). - Washington professional licence transactions - the DOL Professional License Transactions dataset (
data.wa.gov, resourceixni-jq78), aggregate counts of original, renewal and reinstatement transactions by programme, licence type, county and date.
Every item carries state (source jurisdiction: wa or ny) and family (contractor, liquor or professional), so results from all sources can be mixed, filtered and joined downstream.
- Filters: state and family selection, text search on holder or licence-type names, date range, and the v1 WA filters (exact licence number, status).
- Incremental delta runs: the default mode delivers only new or changed rows for the same filter scope. Changed rows keep their original
firstSeenand getlastChanged, so you can detect licence updates daily. - Full bulk export:
fetchAll: truere-reads the selected datasets. - Normalized output: dates become
YYYY-MM-DD, missing values becomenull, and the WA specialty is a nested{ code, name }object. - Schema validation: every item is validated against
.actor/dataset_schema.jsonbefore it reaches the dataset. An invalid item fails the run. - No charge for noise: invalid rows, duplicates, personal-data-excluded rows and rows already delivered unchanged by a previous incremental run are never pushed, so they are never billed.
Privacy: business records only
The sources contain personal columns. This Actor never selects them from the APIs, so they are never downloaded, stored or published.
- WA contractor:
businessNameis published only for registered entity types (LLC, corporation, partnership, LLP, joint venture, association, trust); for individual licences it isnull. The principal name, phone, street address and ZIP columns are never requested. - NY liquor: the dataset has no entity-type flag, so the Actor publishes a row only when the licence holder is confirmed as an organization by a corporate marker in the legal name. Sole-proprietor rows (about 3% of the dataset, including chains without a legal suffix) are excluded entirely instead of risking a natural person's data. ZIP and georeference columns are never selected.
- WA professional: the source is aggregate counts only - no licence holder exists in it.
- The output schema is closed (
additionalProperties: false): a row with any extra field, including a personal one, is rejected before it can be pushed.
Source, licence and access
- WA contractor: L&I Contractor License Data - General, Open Data Commons PDDL (public domain).
- NY liquor: Current Liquor Authority Active Licenses, published by the New York State Liquor Authority on the official state open-data portal (Open NY program).
- WA professional: Professional License Transactions, Open Data Commons ODbL. The Actor names the Washington State Department of Licensing as the source and ships the source's own records, so the license terms are honoured with attribution and licensing notice.
- robots.txt: read on 2026-10-25 for both portals.
User-agent: *declaresCrawl-delay: 1on both hosts; filter URLs under/browseand/catalogare disallowed. The Actor only calls the exact/resource/<id>.jsonendpoints, waits at least 1000 ms between requests and revalidates every redirect destination against the same allowlist. Full details in the repository'sdocs/legal-gate.md. - Not included: the NMLS registry (
nmlsconsumeraccess.org) blocks automated access with 403 + CAPTCHA and is excluded on purpose.
Input
| Field | Type | Default | Description |
|---|---|---|---|
states | array | all | Source states to query: ny, wa. Empty means all sources. |
families | array | all | Families to query: contractor, liquor, professional. Empty means all. |
licenseNumber | string | - | Optional exact WA contractor licence number. When set (or when status is set), only the WA contractor source is queried, as in v1. |
status | enum | - | Optional WA contractor status: ACTIVE, INACTIVE, EXPIRED, SUSPENDED. Other source statuses are returned when no filter is set. |
q | string | - | Optional case-insensitive text filter on holder or licence-type names (never on personal columns). |
dateFrom / dateTo | date | - | Optional range on the source's effective/transaction date (dateTo is exclusive). |
maxResults | integer 1-5000 | - | Total row cap across all selected sources, split evenly between them (the first sources take the remainder). Overrides maxRows when set. |
maxRows | integer 1-5000 | 100 | Legacy v1 total row cap (default 100, split evenly across sources). Ignored when maxResults is set. |
fetchAll | boolean | false | true fetches the full selected datasets, ignoring row caps and the delta state. |
strictSchema | boolean | true | Fail the run when an item fails schema validation. Keep enabled. |
Minimum input
Every field is optional. This is the smallest real input that runs:
{}
With {} the Actor returns up to 100 rows split evenly across the three sources and bills about 0.20 USD. Raise maxResults for larger extracts.
A single-licence WA lookup (v1 compatible):
{"licenseNumber": "WHIDBIT745PO"}
New York liquor licences only:
{"states": ["ny"],"families": ["liquor"],"maxResults": 1000}
Example output
A New York liquor licence:
{"url": "https://data.ny.gov/resource/9s3h-dpkz.json?licensepermitid=0032-23-139868","title": "OSPREYS DOMINION VINEYARDS LTD","scrapedAt": "2026-10-25T02:00:00.000Z","state": "ny","family": "liquor","licenseNumber": "0032-23-139868","businessName": "OSPREYS DOMINION VINEYARDS LTD","businessType": null,"licenseType": "Farm winery","licenseClass": "0032","specialty": null,"status": "active","issuedAt": "2023-05-01","firstSeen": "2026-10-25T02:00:00.000Z","lastChanged": null,"premisesAddress": "44075 MAIN RD, PECONIC","county": "Suffolk","businessState": null,"effectiveDate": "2023-05-01","expirationDate": "2026-04-30","city": "PECONIC","ubi": null,"programType": null,"transactionType": null,"transactionCount": null,"transactionDate": null,"sourceUrl": "https://data.ny.gov/d/9s3h-dpkz"}
A WA contractor licence keeps the v1 fields (licenseNumber, businessName, specialty, effectiveDate, ubi, ...) and adds state: "wa", family: "contractor" and the delta fields. A WA professional aggregate row has licenseNumber: null, status: null and carries programType, transactionType, transactionCount and transactionDate.
Schema note (v0.2): the state field now holds the source jurisdiction (wa/ny). The WA licence holder's address state, previously published in state, moved to businessState so the unified schema stays closed and explicit.
Output fields
| Field | Type | Source |
|---|---|---|
url | URI | public Socrata API URL for this record (envelope) |
title | string or null | business name for entities; licence number; record label for aggregates |
scrapedAt | date-time | run timestamp (envelope) |
state | string | source jurisdiction: wa or ny |
family | string | contractor, liquor or professional |
licenseNumber | string or null | licence number; null for aggregate rows |
businessName | string or null | holder name, only for confirmed organizations |
businessType | string or null | WA contractor businesstypecodedesc |
licenseType | string or null | licence type or class description |
licenseClass | string or null | class code |
specialty | object or null | WA contractor specialtycode1 + specialtycode1desc as { code, name } |
status | string or null | licence status; null for aggregates |
issuedAt | date or null | original issue date (NY) |
firstSeen | date-time | first run that delivered the row |
lastChanged | date-time or null | run in which the row content changed |
premisesAddress | string or null | licensed premises, address and city (NY) |
county | string or null | premises county (NY) or aggregate county (WA professional) |
businessState | string or null | WA holder address state (v1 state semantics); licensees' state for WA professional aggregates |
effectiveDate / expirationDate | date or null | licence dates |
city | string or null | holder or premises city |
ubi | string or null | WA Unified Business Identifier |
programType / transactionType / transactionCount / transactionDate | see dataset schema | WA professional aggregate dimensions and count |
Incremental runs and delta detection
fetchAll: false(default): each source persists what it has delivered for the current filter scope. A later run delivers only rows that are new or whose content changed (row content hash). New rows getfirstSeen = this run,lastChanged = null; changed rows keep the originalfirstSeenand getlastChanged = this run. Unchanged rows are skipped and billed nothing.fetchAll: true: a full export. It ignores the persisted state and returns the complete current selection withfirstSeen = this runandlastChanged = null.
The scope is the pair of filters you use (v1: licenseNumber + status; multi-state: states + families + q + dates). Change any filter to start a separate incremental series.
Limitation, stated honestly: for the WA contractor source the dedupe key is still licence number + effective date, as in v1: a status-only change of an existing licence is not re-emitted incrementally - use fetchAll: true to re-read it. The NY and WA professional sources use a full row-content hash, so any change in the source row is detected.
Pricing
Pay-per-event, with the two synthetic platform events only:
| Event | When it fires | Price |
|---|---|---|
apify-actor-start | Once per run start (includes the first 5 s of compute for free) | 0.00005 USD |
apify-default-dataset-item | One row saved to the dataset: a WA contractor licence, a NY liquor licence or a WA professional transaction record | 0.002 USD = 2.00 USD per 1,000 results |
One row = one charged result, never a page or a retry. The price is unchanged from v0.1.
Works on the Apify free plan - about 2,499 rows for the $5 monthly credit ((5.00 USD - 0.00005 USD run-start) / 0.002 USD per row).
Cost per 1,000 results
Measured locally on 2026-10-22 over 5,000 real WA contractor rows: extraction plus schema validation took 151 ms (30.3 ms per 1,000 rows) with zero schema rejections. The two new sources use the same JSON + validation pipeline, so the per-row cost stays in the same range (see docs/measurements.md).
The real cost driver is the robots-mandated 1 s delay between page requests: a full 161,000-row WA scan makes about 33 requests, so roughly 33 s of wall time. Even with a deliberately conservative allowance for compute, dataset writes and data transfer, the total stays below 0.001 USD per 1,000 results - a gross margin above 99% at the 2.00 USD price. The first paid run will confirm the billed figure.
FAQ
Do I need an API key? No. All three Socrata resources are public and keyless.
Does it scrape the website? No. It calls the official JSON APIs, which is faster and cheaper than rendering the web front ends.
Can I get personal contact data? No. The Actor never requests principal name, phone, street address or ZIP columns for WA contractor licences; it excludes every NY row whose holder is not a confirmed organization; and the WA professional source is aggregate counts only. The goal is business licence data, not people.
Which fields exist per family? WA contractor rows fill the v1 fields (licence number, business name, specialty, UBI, dates). NY liquor rows fill licenseNumber, businessName, premisesAddress, county, licenseType and the licence dates. WA professional rows carry programType, transactionType, transactionCount and transactionDate, with licenseNumber, businessName and status null. All rows always carry state, family, firstSeen and lastChanged.
How do I monitor new licences every day? Run incrementally (default) with your filters. You pay only for rows that are new or changed; unchanged days cost nothing.
Why did my NY run return fewer rows than the source has? The Actor excludes rows whose holder cannot be confirmed as an organization (sole proprietors) to keep natural persons' data out of the output. That is about 3% of the NY dataset.
How do I re-extract everything? Run with fetchAll: true. This is a paid bulk export of the selected datasets.
Why did my second incremental run return zero rows? Because the scope was already fully delivered and nothing changed. That is the intended behaviour and it costs nothing.
Maintenance
tested daily. Steadfetch runs a catalog test every day against the live APIs, validates the output against the published schema and turns failures into issues in the repository. The sources are stable government Socrata datasets, and the Actor has no browser, proxy or anti-bot dependency.