State trade licence records, with change detection
Pricing
$2.00 / 1,000 licence records
State trade licence records, with change detection
Normalised trade-licence records built from the licensing authority's own published data file, with per-run change flags (new, renewed, expired, status-changed, removed). Official downloads only — no lookup-form automation. Street addresses and telephone numbers are never emitted.
Pricing
$2.00 / 1,000 licence records
Rating
0.0
(0)
Developer
Quietfetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Normalised trade-licence records built from the licensing authority's own published licence list, read from the state's official open-data API, with a change flag on every row: what is new, what was renewed, what lapsed, what dropped out of the register since your last run.
Spec (10 lines)
- What: deduplicated, normalised trade-licence records for one or more US states, sourced from each authority's own published licence list, with per-run change flags.
- Buyer: property managers, insurers, marketplaces and lead tools that must verify or monitor licences at scale and cannot poll a lookup form.
- Input:
programs(which licence types),licenseTypes(optional filter),onlyChanged,includeRemoved,snapshotStoreName,maxRecordsPerProgram,pageSize. - Output: one row per licence —
state, authority, program, licenseNumber, licenseType, licenseSubtype, status, statusBasis, businessName, city, stateCode, county, issuedDate, expiresDate, continuingEducation, sourceUrl, datasetPage, sourceFileDate, fetchedAt, recordKey, change, previousStatus, previousExpiresDate, complete. - Pricing: pay-per-event
license-recordat $0.002 ($2 per 1,000 rows).onlyChangedcharges only for rows that moved. - Legal floor: official open data only — the state portal's read-only API, robots.txt and its Crawl-delay checked and obeyed, terms recorded per state. No lookup-form automation, no enrichment, no personal-data resale.
- How it works: page through one licence type on the state's open-data API → normalise → diff against your own snapshot of the previous run → write rows with change flags.
- Tests: golden normalisation, date and status derivation, paging and robots handling, change detection, and a full run against a local stand-in for the portal. No network needed.
- Health: a nightly cheap run reads the first couple of thousand licences of one programme and checks the row shape and the dataset's last-updated date.
- Not built: anything that automates a state's individual-verification lookup form. Where an authority publishes no licence list with acceptable terms, that state is simply not supported.
Source
Texas licences come from "TDLR - All Licenses" on the Texas Open Data Portal (https://data.texas.gov/dataset/TDLR-All-Licenses/7358-krk7), published under the attribution of the Texas Department of Licensing and Regulation and refreshed daily. Each programme is one licence type in that list:
| Programme | Licence type in the source | Licences (Sept 2026, approx.) |
|---|---|---|
tx-ac-contractors | A/C Contractor | 20,400 |
tx-electrical-contractors | Electrical Contractor | 14,000 |
tx-water-well-drillers | Water Well Driller/Pump Installer | 1,750 |
tx-electrical-sign-contractors | Electrical Sign Contractor | 650 |
tx-elevator-contractors | Elevator Contractor | 370 |
Only business (contractor) licence types are offered. The same list also covers individual practitioners; those are deliberately not included.
What is never in the output
Street addresses, telephone numbers, owner names, postcodes and email addresses are never in the output — they are not even requested. The source list carries the licence holder's business and mailing addresses, both telephone numbers, the owner's personal name and a map point. This actor asks the API only for the licence columns it needs, so those fields never cross the wire, never reach the actor, and are not available behind any option. For a sole trader, an address and a phone number are a home address and a personal number, and this actor is not a channel for reselling them.
What is published is the licence: who trades under it, in which town and county, of what type and subtype, and until when. That is what the authority publishes for public verification. The town is taken from the business address block without its postcode, because a town alone is coarse and a town plus a postcode is not.
city is empty wherever the authority publishes no business town — which, in the current source, is every A/C contractor. county is filled for about 96% of those licences and is the better geographic field for that programme.
Status is derived, and says so
The authority's licence list carries no status column — only an expiry date. This actor does not invent one. It sets status to active or expired by comparing the expiry date with the dataset's own last-updated date, and every record carries statusBasis spelling out exactly that comparison. issuedDate is null because the list does not publish one; it is never estimated.
Change flags
change | Meaning |
|---|---|
new | Not in the previous read (every row on your first run) |
renewed | Expiry date moved later |
expired | Now past its expiry date, and was not before |
status-changed | Status moved some other way |
updated | Trading name, town or expiry changed without being a renewal |
removed | Was in the previous complete read, absent from this one |
unchanged | Nothing moved |
Two honesty rules apply to the diff:
- A partial run never reports removals. If you cap records or filter by licence type, everything past the cut would otherwise look as though it had vanished from the register. Such a run is marked
complete: falseand reports no removals. - A partial, refused or failed run never overwrites your snapshot. Otherwise the following good run would report the whole programme as newly added.
A licence whose town or trading name is blank in the source stays unchanged from run to run while nothing about it moves — a blank field is not a change, and you are not charged for it under onlyChanged.
Input example
{"programs": ["tx-ac-contractors"],"onlyChanged": true,"includeRemoved": true,"snapshotStoreName": "state-license-snapshots"}
Output example (one row, second run)
{"state": "TX","authority": "Texas Department of Licensing and Regulation","program": "Electrical Contractors","programId": "tx-electrical-contractors","licenseNumber": "31522","licenseType": "Electrical Contractor","licenseSubtype": null,"status": "active","statusBasis": "derived: LICENSE EXPIRATION DATE (2028-03-01) compared with the dataset's last-updated date (2026-09-26); this authority's licence list publishes no status column","businessName": "Lone Oak Electric LLC","city": "Waco","stateCode": "TX","county": "McLennan","issuedDate": null,"expiresDate": "2028-03-01","continuingEducation": "N","change": "renewed","previousStatus": "expired","previousExpiresDate": "2026-03-01","sourceUrl": "https://data.texas.gov/resource/7358-krk7.json","datasetPage": "https://data.texas.gov/dataset/TDLR-All-Licenses/7358-krk7","sourceFileDate": "2026-09-26","complete": true}
(The licence number and business in this example are illustrative, not a real record.)
When a programme cannot be read
A programme that is refused, disallowed by robots.txt, returns something other than the expected JSON, or is unreachable produces a single row with outcome: "problem" and the reason, visible in the Problems dataset view. Those rows are never charged for, and nothing is worked around: no proxy rotation, no fingerprint spoofing, no CAPTCHA handling. A refusal is reported and the run moves on.
Pricing (pay-per-event)
| Event | When charged | Price |
|---|---|---|
license-record | after a record is written to the dataset | $0.002 ($2 / 1,000 records) |
A full first read charges once per record — about $41 for the whole A/C contractor programme (~20,400 licences), about $28 for electrical contractors, and under $4 for each of the three smaller programmes. After that, onlyChanged: true charges only for the licences that actually moved. Set a maximum charge on the run if you want a hard ceiling; the actor stops writing, and stops charging, when it is reached.
Attribution
The ATTRIBUTION record in the run's key-value store names, per programme, the authority, the dataset page, the dataset's last-updated date and the date it was copied, together with a statement that no state endorsement is implied. The source authority's site policy asks anyone republishing its web content to carry exactly that — if you republish these records onward, carry it too.
Lawful and responsible use
- Official open data only. Read-only requests to the state portal's public API for the licence list the authority publishes. No lookup-form automation, no session, no login.
- robots.txt is fetched for every host and obeyed (
QuietfetchBot, then*), including itsCrawl-delay. A disallowed path is not fetched, and the run says so. - Rate: at least one second between requests to a host, or the host's Crawl-delay if longer. Pages are ordered on the portal's own row id, so no page is fetched twice.
- Identification: the user agent names the bot and a contact address. Bot token
QuietfetchBot; contact page https://quietfetch.com/bots, mailbox hello@quietfetch.com. - Optional app token: if you set a
SOCRATA_APP_TOKENenvironment variable on the actor, it is sent as the portal requests, in a header and never in a URL. It is not needed for normal use. - Data: the licence facts an authority publishes for public verification. No street addresses, no telephone numbers, no owner names, no postcodes, no email addresses, no enrichment from other sources. Buyers are responsible for their own use of the output, including any obligations that attach to the source data.
- Terms: each supported state's terms are recorded verbatim, with their source URL and the date they were read, before that state ships.
Local development
npm test # the full suite, no network requirednpm start # a local run (needs the Apify SDK and an input)
Built and maintained with AI assistance under human review.