← Guides

How-to

How to run Virginia Contractor License Lookup (DPOR Bulk)

Step-by-step: the exact input, what each field does, and the rows that come back from VA DPOR.

The honest version first: if you have one Virginia contractor to check, and you need to know their status today, do not use this actor. Go to DPOR’s own license lookup, type the name, and read the answer. It’s free and it’s current. This actor reads a different DPOR product — the bulk “Regulant Lists” the department publishes as downloadable tab-delimited files — and those files are refreshed only every five business days or so. That gap is the whole trade-off, and it’s the first thing you should know: a licence revoked yesterday can still read as current here. What you get in exchange is the entire roster. Not one contractor at a time, but tens of thousands of Class A, B and C firms and tradespeople as structured rows, filtered by name or city, in a single run — the job where the manual portal collapses, because it is one search, one detail page and one copy-paste per contractor, and there are fifty thousand of them.

What you’ll need

Nothing but an Apify account. The actor talks to VA DPOR directly.

No API key, no CAPTCHA solver, no proxy subscription, no account with the state. This is the least hostile target in the whole suite: DPOR puts the regulant lists on a public URL as plain text files, and the actor simply downloads them over HTTPS and parses them. There is no search form to defeat, no session to hold, and no third-party bill hiding behind the run — just the actor’s per-result fee and Apify’s compute.

Step 1 — Open the actor

Open Virginia Contractor License Lookup (DPOR Bulk) on the Apify Store and press Try for free. The actor’s own page on this site lists its live health and pricing.

The Virginia Contractor License Lookup (DPOR Bulk) 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 Virginia Contractor License Lookup (DPOR Bulk) in the Apify Console
The input form, as it appears in the Apify Console.
FieldTypeRequiredWhat it does
licenseTypesarraynoWhich DPOR license categories to include. Leave empty for Class A, B, and C contractors. Valid values: class_a, class_a_legacy, class_b, class_c, temporary,… (default ['class_a', 'class_b', 'class_c'])
nameFilterstringnoOnly return records where the individual or business name contains this text (case-insensitive). Leave blank for all.
cityFilterstringnoOnly return records where the city contains this text (case-insensitive). Leave blank for all.
maxResultsintegernoCap on the number of unique license records to return per run. (default 500)

Two fields decide what you get back, and they interact in a way that catches people on the first run.

licenseTypes picks which files get downloaded. One entry, one file, one HTTP GET. The default (class_a, class_b, class_c) is the three main contractor classes; the rest of the list — class_a_legacy, temporary, residential_tradesman, backflow_prevention, elevator_mechanic, water_well_provider, accessibility_mechanic, energy_analyst, energy_analyst_firm, sprinkler_inspector — are the specialty rosters, and they’re the reason to touch this field at all. If you want elevator mechanics, ask for elevator mechanics; nothing else will surface them.

maxResults (default 500) is a hard stop, not a sample. The actor walks the requested types in order and stops the moment it has that many records, so with no filters and the default input you do not get a cross-section of Virginia contractors — you get the first 500 rows of the Class A file and nothing else. That is almost never what someone meant. Either narrow with nameFilter / cityFilter (both plain case-insensitive substring matches against, respectively, the individual or business name, and the city) so the cap is never reached, or raise maxResults to something that actually covers the roster you asked for. There are tens of thousands of records behind the default three types.

A working input:

{
  "licenseTypes": [
    "class_a",
    "class_b",
    "class_c"
  ],
  "nameFilter": "",
  "cityFilter": "",
  "maxResults": 500
}

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.

The work is one download per requested licence type, then parsing in memory — so the run is short and its length barely depends on how many rows you keep. A default run stops at 500 rows because that’s the cap; a filtered run returns however many records in Virginia actually match, which for a city filter like RICHMOND is a few thousand and for a specific firm name is a handful.

An empty run is nearly always the filter, not the actor. The filters are literal substring matches against DPOR’s own spelling, which is upper-case and legal: a firm you know as “Smith & Sons” may be filed as SMITH AND SONS INC, so the ampersand kills the match. Type a short, distinctive fragment (SMITH AND SONS, or just NELLIE) rather than the full name off an invoice. The same goes for city — it matches the city field as printed, so a suburb is not the same string as the metro it sits in. And check licenseTypes: a residential tradesperson will never appear in a Class A search, no matter how you spell them.

Step 4 — Read the output

Each run returns a labelled table, not raw JSON:

The output fields returned by Virginia Contractor License Lookup (DPOR Bulk)
The output view — every field the actor returns, named and typed.

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

{
  "license_number": "082040",
  "license_type": "Class A Contractor",
  "license_rank": "A",
  "business_name": "CORNERSTONE ENGINEERING AND CONSULTING SERVICES PLLC",
  "individual_name": null,
  "address": "1272 NELLIE JONES RD",
  "city": "LA CROSSE",
  "state": "VA",
  "zip_code": "23950",
  "country": null,
  "expiration_date": "02/29/2028",
  "certification_date": "02/03/2004",
  "specialties": [
    "EMW",
    "ENV"
  ],
  "email": "bdougherty@cornerstoneusa.net",
  "source_url": "https://www.dpor.virginia.gov/RegulantLists"
}
FieldExample valueMeaning
license_number082040DPOR certificate number (e.g. 082040)
license_typeClass A ContractorLicense category (Class A Contractor, Class B Contractor, etc.)
license_rankADPOR rank code (A, B, C, RETR, BPDW, CEM, etc.)
business_nameCORNERSTONE ENGINEERING AND CONSULTING SERVICES PLLCLicensed business name (for firm licenses)
individual_nameLicensee personal name (for individual licenses)
address1272 NELLIE JONES RDStreet address
cityLA CROSSECity
stateVAState code

Most people came here for a status field, and this is where you need to read carefully: there isn’t one. DPOR’s regulant lists carry no status column at all. The nearest thing is expiration_date (MM/DD/YYYY), and it is a proxy, not an answer — it tells you when the licence was set to lapse, not whether it has since been suspended, revoked, or surrendered. Layer the five-business-day refresh on top and the conclusion is plain: this dataset is fine for building a list, and it is not sufficient for a compliance decision about a named contractor on a given day. If the answer matters legally, confirm that one licence against DPOR’s live lookup before you act on it.

The rest of the columns behave as the file does. A record is either a firm or a person, so exactly one of business_name / individual_name is populated and the other is null — the sample row above is a firm. specialties is a list of DPOR’s internal codes (EMW, ENV, CBC, ELE, H/H), not readable labels; DPOR publishes the legend, and you’ll want it beside you. email is present for many but not all licensees, and there are no phone numbers anywhere — DPOR deliberately excludes them from these files, so no scraper can produce them. license_number is a zero-padded string (082040), so keep it as text; the moment a spreadsheet reads it as a number you lose the leading zero and the key no longer joins.

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.004 per licence record returned. A run that matches nothing returns nothing and is not charged. The default 500-row run is $2.00. Filtering the Class A/B/C files down to one city — call it 2,000 contractors — is $8.00. Pulling a full roster of tens of thousands of records is priced the same way, so decide what you actually need before you raise maxResults. Apify’s platform compute is billed separately, per your plan.

And be clear about what you’re paying for, because the underlying files are free. DPOR gives them away; anyone can download them. You’re paying for the parsing, the filtering, the deduplication and the schedulability — not for access. If you’re comfortable pulling tab-delimited text files yourself and joining them, do that instead; nothing here is gated.

Where the data comes from

This reads VA DPOR directly. The target is Official bulk data files - DPOR publishes a tab-delimited regulant list per licence type; each is downloaded and filtered client-side. There is no search form at all. Every night a canary runs this actor against that live source and diffs the result against a frozen fixture — what “verified” means.

DPOR’s Regulant Lists are exactly what they sound like: for each licence type the department dumps its register to a tab-delimited text file and puts it on a URL. No auth, no CAPTCHA, no form, no anti-bot wall — which makes this the gentlest target in the suite and, honestly, the least clever actor in it. The annoyance isn’t getting the data, it’s using it. The files are one-per-licence-type, so a question spanning classes A, B and C is three downloads before you can even start; the columns are DPOR’s internal names (CERTIFICATE #, FIVE DIGIT ZIP CODE, LICENSE SPECIALTY) with the address split across two lines and the ZIP across two fields; and the specialties arrive as a space-separated string of codes. The actor’s whole value is that it normalises all of that into one flat, typed row per licensee.

The freshness caveat belongs here too, one last time, because it is a property of the source and no actor can fix it: DPOR regenerates these files roughly every five business days. Every run fetches whatever file DPOR is currently serving. That is current-as-of-the-last-refresh, not live.

See it used

List Class A Contractors in a Virginia City filters DPOR’s published list down to Class A contractors in one city.

Two neighbours worth knowing about. Reach for Multi-State Contractor & Trade License Lookup when Virginia isn’t the whole question — it checks the same contractor across several states in one run, which is what you want when a firm works across a border. And if you need a live, per-contractor verification rather than a bulk roster — the thing this actor deliberately cannot give you — a state lookup that queries the registry at request time, like California Contractor License Lookup & Verify (CSLB), is the shape of tool to use.

Last updated 2026-07-13