← Guides

How-to

How to run Texas Contractor Directory & Leads Scraper (TDLR)

Step-by-step: the exact input, what each field does, and the rows that come back from TDLR (Socrata).

You want every licensed A/C contractor in Travis County, or every electrician in Harris, as rows you can filter and load into a CRM. Be clear about one thing before you spend anything: this is the easiest target in the whole suite. TDLR publishes its full licensee roster as open data on data.texas.gov, and the Socrata API behind it is public, documented and free — you can query it with curl right now, no account, no key. If a single ad-hoc pull is all you need, do that; nothing here is gated. The manual alternative that actually hurts is the other one — searching TDLR’s public lookup page licensee by licensee, which returns one record at a time and has no county export at all. This actor sits between the two: it turns the county-scoped query into an input form, maps Socrata’s raw columns onto a stable named schema, and re-runs on a schedule, and a nightly canary checks the columns still mean what they meant yesterday. You’re paying for the schema and the verification, not for access.

The scope caveat, up front, because it decides whether this actor is any use to you: there is no statewide Texas general-contractor licence. TDLR licenses regulated trades — A/C contractors, electricians, appliance installers, boiler operators and dozens of others — and if you go looking for “general contractors in Dallas County” you will find none, because Texas does not license them. Plumbers are also absent: they are licensed by the Texas State Board of Plumbing Examiners, a different agency with a different dataset, and they are not in this one. (The input hints and the field descriptions mention “Plumber” as an example license type; treat that as a placeholder, not a promise — a licenseType of Plumber will come back empty.) If your list has to include GCs or plumbers, this dataset cannot give them to you at any price.

What you’ll need

Nothing but an Apify account. The actor talks to TDLR (Socrata) directly.

No API key, no proxy, no CAPTCHA credentials, nothing to sign up for. Socrata rate-limits anonymous callers, and there is an optional app token that would raise that limit, but it is unset by default and you do not need to supply one — county-scoped runs are small enough that the anonymous throttle is not something you’ll hit. Retries and backoff on a 429 are handled inside the actor.

Step 1 — Open the actor

Open Texas Contractor Directory & Leads Scraper (TDLR) on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.

The Texas Contractor Directory & Leads Scraper (TDLR) listing on the Apify Store
The listing on the Apify Store. Pricing and the nightly health record are both public.

Step 2 — Fill in the input

The input form for Texas Contractor Directory & Leads Scraper (TDLR) in the Apify Console
The input form, as it appears in the Apify Console.
FieldTypeRequiredWhat it does
countystringnoTexas county name to browse (e.g. TRAVIS, ‘Harris County’, DALLAS). Case, spacing and a trailing ‘County’ do not matter. If it is missing or is not a Texas county, the run returns the free county list instead of failing.
licenseTypestringnoOptional substring filter on TDLR’s license-type text (e.g. ‘Electrical Contractor’, ‘A/C Contractor’). Decides whether you get contact data — see below. Leave blank to return all license types in the county.
maxResultsintegernoCap on the number of records returned per county entry, not per run. Default 500; raise to pull an entire county roster. (default 500)

county does most of the work — it becomes a server-side filter on the Socrata query, so the API only ever sends back the county you asked for. Give it a county name (TRAVIS, Harris County, dallas — case, spacing and the “County” suffix are folded before matching), not a city: Austin is not a county and will not resolve. A name that matches no Texas county doesn’t fail the run and doesn’t come back empty either — you get the county list, free, so you can pick the exact name and re-run. Leaving county empty does the same. The county recorded is the county of the licensee’s business address on file, so a contractor who works in Travis but registers a mailing address in Williamson shows up under Williamson.

licenseType is where first-timers go wrong. It is a substring match against TDLR’s own license-type string, not a trade name of your choosing — A/C Contractor and Electrician match, HVAC and aircon match nothing, and Plumber matches nothing because plumbers aren’t in this dataset at all. Leave it blank the first time and look at the license_type values that come back; that’s the authoritative list of what you can filter on. Because it’s a substring, a short one is broad on purpose: Electric will pull master electricians, journeymen, apprentices and electrical contractors together.

It also decides whether the run produces an outreach list or a roster, which is the one thing to get right before you spend anything: the establishment and company licence types carry a phone and address on effectively every row, and the individually held trade licences carry none at all. Electrical Contractor gives you 13,911 phones out of 13,917 rows statewide; A/C Contractor gives you 0 out of 20,323. There is more on this under Step 4.

maxResults is a cap, not a filter, and it defaults to 500 — far below a full county roster in any of the big metros. Leave the default in place on an unfiltered county run and you’ll get the first 500 rows by license number and nothing else, which is a cheap way to see what a county contains before you commit. Raise it deliberately when you actually want the whole roster; the actor pages through Socrata 1,000 rows at a time until it has them or hits your cap.

A working input:

{
  "county": "TRAVIS",
  "licenseType": "Electrical Contractor",
  "maxResults": 200
}

Step 3 — Run it

Press Start. Rows are pushed to the dataset as they’re found, so the run log fills in as it works.

A healthy run is quick and quiet — it’s a JSON API, not a browser, so there is no page to render and no session to hold. The log tells you how many rows came back from the county query, and then the rows land in the dataset. Run times and row counts aren’t measured here, so they’re not quoted: a filtered trade in a rural county is a single request, an unfiltered run against a metro county is many pages of a thousand rows. You’ll see the real number in the log within seconds.

An empty run is almost always the input. A county name that isn’t a Texas county, a licenseType string TDLR doesn’t use, or a trade TDLR simply doesn’t license (general contractor, plumber) all return zero rows. Zero rows is free — the actor charges per record — so a wrong filter costs you nothing but the compute. Re-run with licenseType blank to confirm the county itself returns rows; if it does, the filter string was the problem.

A run can’t leave you guessing between “nothing matched” and “the state’s API was down”, because those two are not allowed to look alike. If data.texas.gov doesn’t answer, the actor doesn’t crash and doesn’t hand back a quiet empty dataset — it returns a single free notice row saying so, with notice_type: "source_unavailable". Notice rows are never charged. Likewise, a county name the dataset doesn’t recognise (or a missing one) comes back as the free list of valid county names rather than a failed run, so you can see the exact spelling and re-run.

Step 4 — Read the output

Each run returns a labelled table, not raw JSON:

The output fields returned by Texas Contractor Directory & Leads Scraper (TDLR)
The output view — every field the actor returns, named and typed.

Every row looks like this — a real row from a real run:

{
  "state": "TX",
  "license_number": "18028",
  "business_name": "DEAN JOHNSTON, INC",
  "person_name": "DEAN JOHNSTON, INC",
  "license_type": "Electrical Contractor",
  "license_subtype": "EC",
  "expiration_date": "08/04/2026",
  "county": "TRAVIS",
  "address": "515 S CONGRESS AVE",
  "address_line2": "SUITE 106",
  "city_state_zip": "AUSTIN TX 78704",
  "phone": "5124773747",
  "source_url": "https://data.texas.gov/dataset/Texas-Department-of-Licensing-and-Regulation/7358-krk7"
}
FieldExample valueMeaning
stateTXAlways ‘TX’ for this actor
license_number18028TDLR license number
business_nameDEAN JOHNSTON, INCLicensed business or licensee name
person_nameDEAN JOHNSTON, INCOwner or individual licensee name
license_typeElectrical ContractorTDLR license type (e.g. Electrical Contractor, A/C Contractor, Master Electrician)
license_subtypeECTDLR subtype code (e.g. EC, BE, REG, CER, RAIC)
expiration_date08/04/2026License expiration date (MM/DD/YYYY)
countyTRAVISTexas county of the business address
address / address_line2515 S CONGRESS AVE / SUITE 106Business street address and suite, when TDLR publishes them
city_state_zipAUSTIN TX 78704City, state and ZIP as one glued string
phone5124773747Business phone, bare digits

Most people came for phone and the address block — that’s the outreach list. The compliance readers came for license_number, license_type and expiration_date, which is the “is this contractor current” set. Both come out of the same run.

Read this before you pick a licenseType, because it decides whether you get an outreach list or a roster. TDLR publishes contact details for about 40 of its licence types, and they are the establishment and company registrations — Electrical Contractor, Full Service Establishment, Mini Establishment, Appliance Installation Contractor, Water Well Driller/Pump Installer, Elevator Contractor and similar. On those, coverage is effectively total: 13,911 of 13,917 Electrical Contractor rows statewide carry a phone, measured 2026-08-21. Individually held licences carry none at all. All 20,323 A/C Contractor rows have a blank phone, address and city/ZIP; so do A/C Technicians, every grade of electrician, and the cosmetology trades. Statewide it works out at 99,720 of 983,494 rows — about 10%. A pull of an individual licence type coming back with null contact columns is the state’s data, not a failed scrape, and no setting changes it.

Things to watch. Every field except state is nullable. business_name and person_name are not reliably both populated: an individual licensee may carry only a person name, a company only a business name. Text arrives exactly as TDLR stores it — names shouted in capitals, person_name in LAST, FIRST order, phones as bare digits with no formatting, dates as MM/DD/YYYY strings — so normalise before you mail-merge or dedupe. license_subtype is a raw TDLR code (BE, REG, CER, RAIC); it means something to TDLR and nothing on its own, so don’t build logic on it without checking what the code means for that licence type. And there are no email addresses in this dataset — TDLR doesn’t publish them, so nothing here can produce them.

No coordinates either, and that one is deliberate. The state’s file carries a latitude/longitude column that looks like a free geocode and is actually a city centroid: Travis County’s 468 Electrical Contractors have 432 distinct addresses and 17 distinct points between them, 347 of them identical. Passing it through would put a city’s worth of contractors on one pin, so the actor drops it — city_state_zip says the same thing with a ZIP attached. Geocode address yourself if you need to map the results.

Step 5 — Export it

Open the Dataset tab and export to CSV, JSON, or Excel — or pull the same rows from the API, which is what you want if this is going to run on a schedule.

What it costs

$0.002 per record. A run that pulls 500 contractors out of Travis County costs $1.00; an unfiltered pull of a large metro county runs into thousands of rows and you should set maxResults with that in mind. Empty runs are free — no rows, no charge — so getting a county name or a licence type wrong costs nothing. Apify’s own platform usage (compute) is billed separately under your plan, on top of the per-record fee, though this actor is a plain API pull and about as cheap as compute gets.

The comparison to make is not against zero, and it would be dishonest to pretend otherwise: the underlying Socrata API is free and open, and a developer willing to read the dataset’s column names and write the paging loop can have this data for nothing. The fee buys the named schema, the county/type filtering wired to an input form, and the nightly check that TDLR hasn’t quietly renamed a column under you. If none of that is worth $0.002 a row to you, go query data.texas.gov directly — that is a legitimate answer.

Where the data comes from

This reads TDLR (Socrata) directly. The target is Socrata SODA JSON API (data.texas.gov, TDLR licensee export) - county filtered server-side, paged 1,000 rows at a time. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.

TDLR publishes its licensee export on data.texas.gov through Socrata, the same open-data platform a lot of states use, and there is no anti-bot wall, no session, no browser and no proxy in the path — this actor is the easiest target in the portfolio and pretending otherwise would be a lie. The awkward part isn’t access, it’s the dataset. Column names are Socrata’s, not English; a county filter has to be expressed as a SoQL $where clause with the county upper-cased to match how it’s stored; results come back a page at a time and have to be walked to the end. And the boundary of the dataset is the thing that catches people out — TDLR is a Texas licensing board, not the Texas licensing board, so “every contractor in Texas” is not a query this or any other source can answer. What the nightly canary is really watching for here is a schema change: an open-data publisher renaming or dropping a column is a quiet break, the run still succeeds and the rows just come back emptier.

See it used

Build a Travis County HVAC Contractor List builds a county HVAC contractor list from TDLR’s open data.

If you already know which contractor you care about, this is the wrong tool. Texas Contractor & Trade License Lookup (TDLR) verifies a single TDLR licence by number or business name — reach for that when you’re checking a bid you just received rather than building a list of people to send bids to. And if the territory you’re prospecting isn’t Texas, California Contractor Directory by Trade & County (CSLB) does the same county-scoped roster job against CSLB, where — unlike Texas — general building contractors are licensed and are in the data.

Last updated 2026-08-21