Verify Contractor Licenses in 11 US States with One API Call

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Verify Contractor Licenses in 11 US States with One API Call

    Verify Contractor Licenses in 11 US States with One API Call

    If you build anything in insurance tech, background checks, credentialing, or construction management, you've probably hit this wall: verifying a contractor's license means scraping 11 different state websites, each with different formats, search flows, and data schemas.


    California uses CSLB. Texas has TDLR. Florida runs DBPR. Washington has L&I with 159K+ records. New York uses municipal open data. None of them agree on what a "license record" looks like.


    I needed this for a compliance project, so I built a single API that normalizes all of it.


    The Problem

    Here's what you're dealing with state by state:


    CA CSLB 300K+ HTML scraping
    TX TDLR 200K+ Search form
    FL DBPR 500K+ HTML table
    NY NYC Open Data 80K+ JSON API
    WA L&I (Socrata) 159K JSON API
    CT DCP 133K Socrata
    OR CCB 56K Includes bond/insurance
    CO DORA 70K Trade licenses
    IL IDFPR 400K+ Statewide
    IA DWD 17K Socrata
    DE DPOS 31K Socrata


    Each state returns different fields. Some include bond info, some include insurance, some just give you name + license number + status. Building scrapers for each one is a multi-week project.


    One Endpoint, Normalized Response





    const response = await fetch(
    'https://marketplace-price-api-production.up.railway.app/license/search?' +
    new URLSearchParams({
    state: 'CA',
    name: 'Johnson Electric',
    type: 'contractor'
    }),
    { headers: { 'X-Api-Key': 'your-key' } }
    );

    const data = await response.json();











    {
    "success": true,
    "total": 3,
    "licenses": [
    {
    "name": "JOHNSON ELECTRIC INC",
    "licenseNumber": "C10-987654",
    "state": "CA",
    "status": "Active",
    "type": "C-10 Electrical",
    "issueDate": "2015-03-12",
    "expirationDate": "2027-03-31",
    "bond": {
    "amount": 25000,
    "company": "Hartford Fire Insurance"
    }
    }
    ]
    }







    Every state returns the same shape: name, license number, status, type, dates. States that have bond/insurance data include it. The normalization layer handles the differences so your code doesn't have to.


    Building a Bulk Verification Pipeline

    The real use case is batch verification. Here's a Node.js script that checks a CSV of contractors against all supported states:






    import { parse } from 'csv-parse/sync';
    import { readFileSync, writeFileSync } from 'fs';

    const API_URL = 'https://marketplace-price-api-production.up.railway.app/license/search';
    const API_KEY = 'your-key';

    const contractors = parse(readFileSync('contractors.csv'), {
    columns: true
    });

    const results = [];

    for (const row of contractors) {
    const resp = await fetch(
    `${API_URL}?${new URLSearchParams({
    state: row.state,
    name: row.company_name,
    type: 'contractor'
    })}`,
    { headers: { 'X-Api-Key': API_KEY } }
    );

    const data = await resp.json();
    const match = data.licenses?.[0];

    results.push({
    company: row.company_name,
    state: row.state,
    verified: match?.status === 'Active',
    licenseNumber: match?.licenseNumber || 'NOT FOUND',
    expiration: match?.expirationDate || 'N/A'
    });

    // Respect rate limits
    await new Promise(r => setTimeout(r, 200));
    }

    writeFileSync('verification-results.json', JSON.stringify(results, null, 2));
    console.log(`Verified ${results.length} contractors`);







    Feed it a CSV with company_name and state columns, get back verification status for each one.


    Expiration Monitoring

    Set up a daily cron to catch licenses about to expire:






    const WATCHLIST = [
    { name: 'ABC Plumbing', state: 'FL', license: 'CFC1234567' },
    { name: 'XYZ Electric', state: 'CA', license: 'C10-987654' },
    ];

    for (const contractor of WATCHLIST) {
    const resp = await fetch(
    `${API_URL}?state=${contractor.state}&license=${co ntractor.license}`,
    { headers: { 'X-Api-Key': API_KEY } }
    );
    const data = await resp.json();
    const lic = data.licenses?.[0];

    if (lic) {
    const daysLeft = Math.floor(
    (new Date(lic.expirationDate) - Date.now()) / 86400000
    );
    if (daysLeft 30) {
    console.log(
    `⚠️ ${contractor.name} (${contractor.state}) ` +
    `expires in ${daysLeft} days`
    );
    }
    }
    }







    Why Not Just Scrape?

    You could build scrapers for each state. I did — that's how this API started. Here's why you probably don't want to maintain them:
    • California CSLB changes their HTML layout every few months
    • Florida DBPR rate-limits aggressively and blocks automated UA strings
    • Oregon CCB includes bond/insurance data nested in secondary pages
    • Washington L&I uses Socrata with non-obvious dataset IDs


    Each state breaks differently, at different times. I handle the maintenance so you don't have to.


    I built this because I kept needing it across projects. It's on RapidAPI — free tier gives you enough calls to evaluate, paid tiers for production volume.




    More...
Working...