Vetting a contractor for a gig platform is nothing like vetting one for a traditional hiring decision. The scale is different (tens of thousands of applicants, not dozens), the speed expectation is different (contractors expect a same-day or faster approval decision), and the automation requirement is different (no human reviewer can process 500 contractor applications per day).

Platforms like Thumbtack, Angi, and their imitators have learned this through painful iteration. Early versions of their vetting processes were largely trust-based: self-reported credentials, light identity checks, and a covenant from the contractor that their license was valid. When that produced headline liability incidents, they added more layers. The result is a multi-step verification funnel that still needs to complete fast enough that a contractor who applied Monday morning is approved and receiving leads by Monday afternoon.

This post is a technical walkthrough of how to build that verification pipeline, with specific focus on the license verification step, the edge cases that will trip up a naive implementation, and the re-vetting requirements once you have tens of thousands of contractors on the platform.

What Makes Gig Platform Vetting Different

Traditional contractor vetting - as done by a property management company or a GC doing subcontractor prequalification - involves a human reviewer who looks at a file and makes a judgment. The turnaround is days to weeks. The volume is tens of contractors per month, not thousands.

Gig platforms need:

The Verification Funnel

A modern gig platform contractor verification funnel has four layers, run sequentially. License verification sits at layer 3:

1
Identity verification - Government ID document scan + selfie match. Confirms the person is who they say they are. Typically handled by a third-party like Persona, Stripe Identity, or Onfido. Takes 30-60 seconds.
2
Background check - Criminal record check, sex offender registry, identity fraud check. Typically handled by Checkr or Sterling. Takes minutes to hours depending on state.
3
License verification - The contractor claims to hold a license in a specific state and trade. Verify that claim against official state board data. Takes seconds via API. This post focuses here.
4
Account activation - All three checks pass, account transitions to active state. Contractor can start receiving jobs.

License verification is the fastest step and the easiest to automate. Unlike background checks (which involve criminal databases with complex matching logic and FCRA compliance requirements), license verification has a straightforward binary outcome: the license is active or it is not, the classification matches or it does not.

What Gets Verified at License Check

The contractor submits their license number and state during the application form. The verification step confirms:

  1. License is active - Not expired, not suspended, not revoked
  2. License belongs to this contractor - The business name or license holder name on the state board record matches the applicant's name (fraud detection - more on this below)
  3. Classification matches the services offered - A contractor registering as a plumber should have a plumbing contractor license, not a general building license that has no plumbing classification
  4. No disqualifying disciplinary history - Platform policy determines the threshold; many platforms auto-decline on active sanctions, flag on resolved complaints

Classification Mapping - Connecting License Types to Platform Job Categories

One of the more technical aspects of license verification for gig platforms is mapping state-specific license classifications to your platform's service categories. A contractor on Thumbtack lists services they offer: plumbing, HVAC, electrical, etc. Your platform's job types need to map to the license classifications that legally authorize that work.

This mapping is state-specific. In California, a plumbing contractor needs a C-36 Plumbing license. In Texas, they need an MP (Master Plumber) license issued through TSBPE. In Florida, a Certified Plumbing Contractor license through DBPR. The same trade, three different state systems, three different classification strings in the API response.

// Classification mapping table (sample - real table covers all 50 states)
const CLASSIFICATION_MAP = {
  'plumbing': {
    CA: ['C-36', 'C-34'],  // C-36 Plumbing, C-34 Pipeline
    TX: ['MP', 'RPL'],     // Master Plumber, Responsible Master Plumber
    FL: ['CFC', 'SCC'],    // Certified Plumbing, Specialty Plumbing
    NY: ['PLUMBER', 'MP']
  },
  'hvac': {
    CA: ['C-20', 'C-38'],
    TX: ['ACR', 'TACL'],
    FL: ['CAC', 'CMC'],
    NY: ['HVAC-MECHANIC']
  },
  'electrical': {
    CA: ['C-10'],
    TX: ['EL', 'ECL'],
    FL: ['EC', 'ER'],
    NY: ['ELECTRICIAN']
  }
};

function classificationMatchesService(apiClassification, serviceType, state) {
  const validClassifications = CLASSIFICATION_MAP[serviceType]?.[state] || [];
  return validClassifications.some(c =>
    apiClassification.toUpperCase().includes(c.toUpperCase())
  );
}

Maintain this mapping as a living document. State boards occasionally add new license classifications, retire old ones, or restructure their classification hierarchy. Build an admin interface that lets your compliance team update the mapping without a code deploy.

Edge Case - Local and County Licensing Layers

Some jurisdictions have a local licensing requirement on top of the state license. The most significant ones your platform will encounter:

You cannot practically verify local permits in real time for every jurisdiction in the country. The pragmatic approach: verify state licenses via API (the tractable problem), and for known high-density markets with additional local requirements, add a flag in the contractor's profile that says "state license verified - local permit required in [City]." Surface that flag to the contractor during onboarding so they know they need to provide local documentation before working those jobs. Do not auto-block; flag for human follow-up.

Fraud Detection - License Number Ownership Verification

A non-obvious fraud vector: a contractor who does not hold a valid license submits the license number of a legitimate licensed contractor in the same state and trade. If your verification only checks that the license number is active and has the right classification, you will approve the fraudulent applicant - because the license number they submitted is valid.

Preventing this requires name matching between the applicant and the license holder of record. The API response should return the name associated with the license. Your verification logic compares that name to the applicant's name from the identity verification step.

async function verifyLicenseOwnership(applicant, licenseNumber, state) {
  const licenseData = await contractorVerifyApi.check({
    licenseNumber,
    state
  });

  if (!licenseData || licenseData.status !== 'active') {
    return { pass: false, reason: 'license_inactive' };
  }

  // Fuzzy match license holder name vs. applicant name
  // (handle LLC vs. Inc, abbreviations, DBA names)
  const nameMatch = fuzzyNameMatch(
    licenseData.license_holder_name,
    applicant.businessName || applicant.fullName
  );

  if (nameMatch < 0.75) {
    return {
      pass: false,
      reason: 'name_mismatch',
      licenseHolder: licenseData.license_holder_name,
      applicantName: applicant.businessName
    };
  }

  return { pass: true, licenseData };
}

// Levenshtein-based fuzzy match, normalized to 0-1
function fuzzyNameMatch(a, b) {
  const normalize = str => str.toLowerCase()
    .replace(/\b(llc|inc|corp|ltd|co)\b\.?/gi, '')
    .replace(/[^a-z0-9 ]/g, '')
    .trim();
  const na = normalize(a);
  const nb = normalize(b);
  const maxLen = Math.max(na.length, nb.length);
  if (maxLen === 0) return 1;
  return 1 - levenshteinDistance(na, nb) / maxLen;
}

Name matching is imperfect. A sole proprietor whose license is under their personal name ("John Smith") may apply using their DBA ("Smith Plumbing Solutions"). Set the match threshold low enough to avoid false rejections, but flag near-misses for human review rather than auto-approving them. The goal is to catch clear fraud (submitting a completely different person's license) while not over-blocking legitimate applicants with DBA or entity name variations.

Performance Requirements - The 3-Second Rule

Contractors applying to a gig platform expect an application experience similar to signing up for any consumer app - responsive, fast, not waiting 30 seconds for a verification step to complete. The license verification call needs to complete within 3 seconds for synchronous implementation to be viable.

If the API call takes longer (which can happen when hitting state boards that are slow to respond), you have two options:

Handling State Board Downtime

State licensing board websites go down. Some go down regularly - several state boards run on aging infrastructure with maintenance windows that are not well-publicized. Your verification service needs a fallback policy for when the data source is temporarily unavailable.

Options, from strictest to most permissive:

For a gig platform with conversion rate pressure, the soft defer option typically wins. Document the policy clearly in your terms of service so you have the right to suspend accounts that fail the deferred verification.

Re-Vetting at Scale - The Quarterly Sweep

Active contractor license management does not end at onboarding. A platform with 50,000 active contractors needs a quarterly re-verification sweep to catch licenses that have expired or been suspended since the contractor joined.

50,000 quarterly verifications sounds large, but batch API calls make it tractable. Structure the sweep as a background job that runs over several days, processing contractors in batches of 100-500:

// Quarterly re-verification sweep
// Run as a scheduled background job, not a single bulk call

async function runQuarterlyReverification() {
  const PAGE_SIZE = 200;
  let page = 0;
  let processedCount = 0;
  let suspendedCount = 0;

  while (true) {
    const contractors = await db.query(
      `SELECT id, business_name, license_number, license_state, service_category
       FROM contractors
       WHERE status = 'active'
       AND (last_verified_at IS NULL OR last_verified_at < NOW() - INTERVAL '90 days')
       ORDER BY last_verified_at ASC NULLS FIRST
       LIMIT $1 OFFSET $2`,
      [PAGE_SIZE, page * PAGE_SIZE]
    );

    if (contractors.rows.length === 0) break;

    const batchResults = await contractorVerifyApi.batchCheck(
      contractors.rows.map(c => ({
        licenseNumber: c.license_number,
        state: c.license_state
      }))
    );

    for (let i = 0; i < contractors.rows.length; i++) {
      const contractor = contractors.rows[i];
      const result = batchResults[i];

      await db.query(
        `UPDATE contractors
         SET last_verified_at = NOW(),
             license_status = $1,
             license_api_response = $2
         WHERE id = $3`,
        [result.status, JSON.stringify(result), contractor.id]
      );

      if (result.status !== 'active') {
        await suspendContractorPendingReview(contractor.id, result.status);
        suspendedCount++;
      }

      processedCount++;
    }

    page++;
    // Brief pause to avoid overloading the API
    await new Promise(resolve => setTimeout(resolve, 500));
  }

  await logSweepSummary({ processedCount, suspendedCount });
}

Mapping License Classifications to Platform Job Types

Once license classification matching is working, close the loop by using it at job assignment time - not just at onboarding. If a contractor's license is for plumbing (C-36) and a customer requests an electrical job, your job routing logic should not offer them that job. Surface the classification mismatch gracefully to the contractor: "This job requires an electrical license. Your current verified license is for plumbing."

This reduces consumer complaints (customers getting contractors who are technically licensed but not for the work they need) and reduces contractor frustration (being offered jobs they cannot legally do).

Metrics to Track

Build dashboards around these verification pipeline metrics:

For deeper context on why manual verification does not work at gig platform scale, Why Manual License Checks Fail at Scale covers the operational math. For integrating the onboarding verification step into a broader contractor activation flow, Integrating License Verification Into Your Onboarding Flow has a complementary implementation guide.

Summary

Gig economy contractor vetting has three distinct requirements that differentiate it from other use cases: volume, speed, and automation. License verification via API addresses all three - it is fast enough for synchronous onboarding (under 3 seconds), consistent enough for automated decision-making, and scalable enough for quarterly sweeps of 50,000+ contractors.

The non-obvious work is in the details: building a maintainable classification mapping table, handling name mismatches for fraud detection without blocking legitimate applicants, defining a fallback policy for state board downtime, and building the quarterly re-verification pipeline that keeps your active contractor pool clean over time.

Get these pieces right and license verification becomes an invisible part of your onboarding funnel - one that protects your platform from liability without creating friction that reduces contractor conversion.