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:
- Volume: Hundreds to thousands of new contractor applications per week at scale
- Speed: Contractors expect near-instant feedback. A 3-day verification wait is a major conversion killer at signup
- Consistency: Every contractor must go through the same checks. Manual review introduces inconsistency that creates legal exposure (disparate treatment claims)
- Automation-first: Human review should be reserved for edge cases and appeals, not the standard path
The Verification Funnel
A modern gig platform contractor verification funnel has four layers, run sequentially. License verification sits at layer 3:
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:
- License is active - Not expired, not suspended, not revoked
- 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)
- 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
- 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:
- New York City - NYC DOB requires a separate registration for electrical contractors operating within the five boroughs, on top of the state electrical license
- Los Angeles - LA City requires a separate permit for some trades in addition to the CSLB state license
- Chicago - City of Chicago electrical licensing is entirely separate from state requirements
- New Orleans - Orleans Parish has its own contractor licensing system that exists alongside state licensing
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:
- Show a "verifying" state - The form submits, the contractor sees a "Verifying your license..." spinner, and the page resolves once the check completes. Acceptable UX for checks up to ~10 seconds.
- Async verification with provisional activation - The contractor is provisionally activated pending license verification, with a notification that they cannot receive job leads until verification completes. Use this if your verification p95 exceeds 10 seconds.
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:
- Hard block: Cannot verify = provisional pending status. Contractor is notified to try again later. Strictest compliance posture but creates friction.
- Soft defer: Provisionally activate for 48 hours with an async verification running in background. If verification fails when the board comes back online, suspend the account.
- Cache-based fallback: If you have a recent (under 7 days) cached result for this exact license number, use it. Good for re-verifications; not useful for first-time applicants.
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:
- Onboarding pass rate by state - High rejection rates in a specific state might indicate a data quality issue with that state's API data, or a classification mapping problem
- Verification latency (p50, p95, p99) - Track API response time to catch degradation before it affects UX
- False rejection rate - Contractors who appealed a rejection and were approved on manual review; high rate indicates overly strict matching logic
- Suspension rate from quarterly sweeps - Baseline this over time. A sudden spike indicates a state board batch of enforcement actions, not a bug
- Fraud catch rate - How many name mismatch flags led to confirmed fraud on manual review
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.