Subcontractor prequalification is the step in the commercial construction process that almost everyone agrees is necessary and almost everyone finds painful. Owners require GCs to prequal their subs. GCs require subs to complete lengthy questionnaires. The subs spend hours filling out paperwork, the GC's compliance team spends days chasing incomplete responses and manually verifying the claims inside them, and somewhere in that process a critical detail - like an expired license or a lapsed bond - slips through undetected.
The information GCs and owners need during prequalification is not mysterious. It comes down to: is this subcontractor legally authorized to do the work, in the state where the project is, for the trade being hired? That question has a definitive answer, and it lives in state licensing board records that are publicly accessible. The problem has never been information availability - it has been the friction involved in accessing it.
This post is a technical guide to replacing the manual license verification step in prequalification with an API-driven workflow that returns authoritative results in seconds, integrates with bid management platforms, and maintains ongoing monitoring for active subs throughout the project period.
What Prequalification Actually Checks
Prequalification requirements vary by project type, owner, and contract value, but the licensing-related information GCs collect consistently across all prequalification workflows includes:
- Active contractor license in the correct state for the project location
- License classification that covers the specific trade being subcontracted (a sub hired for mechanical work needs a mechanical contractor license, not just a general building license)
- Active bond with a bond amount meeting the GC's or owner's minimum threshold
- No pending disciplinary actions or active sanctions from the state licensing board
- Insurance certificates (general liability, workers comp) - these come from the insurance carrier, not the licensing board, so they are a separate document collection step
License verification via API addresses the first four items. Insurance certificate collection remains a manual document intake step - but it becomes much faster once the automated license check has already cleared the majority of subs, and human reviewers only handle the insurance documents for qualified candidates.
Why the Traditional Process Is Broken
The standard prequalification workflow looks like this: the GC sends the sub a questionnaire (often via BuildingConnected, PlanHub, or a homegrown portal). The sub fills it out, uploads license copies, and submits. Someone on the GC's compliance team reviews the submission, looks at the license copy, and tries to verify it against the state board website. They check two or three states' board websites, get frustrated when one of them is slow or has changed its search interface, flag three submissions as "needs follow-up," and move to the next item in the queue. The follow-up emails sit in someone's inbox for a week.
By the time the sub list is "verified," the project bid due date is close, the GC is making go/no-go decisions under time pressure, and some of the license verification was done superficially. The copy of the license uploaded by the sub may have been expired when they uploaded it. No one checked the expiration date against the current calendar.
The root problem: manual verification does not scale to the number of subs involved in a commercial project. A 30-story mixed-use development might have 80 subcontractors across 25 trades. Manually verifying each one's license in the correct state takes 2-4 hours if everything goes smoothly. Nothing ever goes that smoothly.
The API-Driven Prequalification Workflow
Here is what the workflow looks like when license verification is automated via API:
Implementation - The Prequalification API Integration
// prequalification.service.js
async function processPrequalSubmission(submission) {
const {
subId,
businessName,
licenseNumber,
licenseState,
tradeType,
projectState
} = submission;
// State where the project is located may differ from where sub is headquartered
// Always verify against the PROJECT state, not the sub's home state
const verifyState = projectState || licenseState;
const licenseData = await contractorVerifyApi.check({
licenseNumber,
state: verifyState,
name: businessName // for ownership validation
});
const verificationRecord = {
subId,
licenseNumber,
state: verifyState,
apiResponse: licenseData,
verifiedAt: new Date().toISOString(),
result: null,
flags: []
};
// Hard fail: license is not active
if (!licenseData || ['suspended', 'revoked', 'expired'].includes(licenseData.status)) {
verificationRecord.result = 'FAIL';
verificationRecord.flags.push({
type: 'license_status',
severity: 'hard',
detail: `License status: ${licenseData?.status || 'not found'}`
});
await saveVerificationRecord(verificationRecord);
await notifySubOfFailure(subId, verificationRecord.flags);
return verificationRecord;
}
// Classification check
const classMatch = checkClassificationMatch(licenseData.classification, tradeType, verifyState);
if (!classMatch.match) {
verificationRecord.flags.push({
type: 'classification_mismatch',
severity: classMatch.partial ? 'soft' : 'hard',
detail: `License classification "${licenseData.classification}" does not cover ${tradeType}`
});
}
// Bond amount check (require $25k minimum for most commercial work)
const BOND_MINIMUM = 25000;
if (licenseData.bond_amount < BOND_MINIMUM) {
verificationRecord.flags.push({
type: 'insufficient_bond',
severity: 'soft',
detail: `Bond amount $${licenseData.bond_amount} is below required $${BOND_MINIMUM}`
});
}
// Disciplinary actions
if (licenseData.disciplinary_actions?.length > 0) {
verificationRecord.flags.push({
type: 'disciplinary_actions',
severity: 'soft',
detail: `${licenseData.disciplinary_actions.length} disciplinary action(s) on record`
});
}
const hardFlags = verificationRecord.flags.filter(f => f.severity === 'hard');
const softFlags = verificationRecord.flags.filter(f => f.severity === 'soft');
if (hardFlags.length > 0) {
verificationRecord.result = 'FAIL';
} else if (softFlags.length > 0) {
verificationRecord.result = 'REVIEW'; // human review queue
} else {
verificationRecord.result = 'PASS';
}
await saveVerificationRecord(verificationRecord);
return verificationRecord;
}
Multi-State Subcontractors
Large specialty subs - national HVAC companies, major electrical contractors, regional mechanical subs - work across multiple states. A single sub bidding on three projects in California, Texas, and Florida needs three separate license verifications, one per state.
During prequalification, collect all states the sub is being considered for and run a batch verification covering each one. Store each verification as a separate record keyed to the state. When evaluating the sub for a specific project, the relevant verification is the one matching the project's state - not a blanket check that happened to use the sub's home state.
// Multi-state batch verification for a single sub
async function verifySubMultiState(sub, projectStates) {
const requests = projectStates.map(state => ({
licenseNumber: sub.licenseNumberByState?.[state] || sub.primaryLicenseNumber,
state,
name: sub.businessName
}));
const results = await contractorVerifyApi.batchCheck(requests);
return projectStates.reduce((acc, state, idx) => {
acc[state] = results[idx];
return acc;
}, {});
}
Classification Matching for the Bid Context
Classification mismatches are more common than they appear. The scenario that comes up regularly: a sub who primarily does general contracting work has an active GC license, but you are hiring them specifically for electrical or plumbing work. Their GC license does not authorize them to perform work that requires a specialty license - even if they have crews that do that work under a separately licensed supervisor.
The correct match check is against the trade type the sub is being hired for, not their general capability. If a GC applies to be a named sub for the MEP (mechanical, electrical, plumbing) scope on your project, they need a C-10 electrical license for the electrical portion, a C-36 plumbing license for plumbing, and so on - in California, at least. In other states, classification rules differ.
A C-10 electrical license that is expired but the contractor has an active B (general building) license is a specific flag worth raising with the sub before bid day. The sub may have let their specialty license lapse without realizing it creates a problem for this bid. Catching it early, before the bid package is submitted, is better for everyone than discovering it during scope negotiation.
Integration with Bid Management Software
The two most widely used bid management platforms in commercial construction are Procore (for project management with bidding) and BuildingConnected (dedicated bid management, owned by Autodesk). Both have APIs that allow external systems to push data into prequalification records.
The integration pattern: your verification service runs as a webhook listener. When a sub's prequalification form is submitted in BuildingConnected, BuildingConnected fires a webhook to your service. Your service calls ContractorVerify, processes the result, and pushes the verification status back into the BuildingConnected prequalification record via their API. The GC's project manager sees the verification result inline in their existing workflow without having to visit a separate tool.
This integration pattern also works with custom-built portals. The verification logic lives in your service layer, not the bid management platform - which means you can run it consistently regardless of which frontend the sub used to submit.
Ongoing Monitoring for Awarded Subs
Prequalification happens before the bid. But projects run for months. A sub whose license was active when they were awarded the contract might have a license suspension 3 months into the project.
For awarded subs, set up a monitoring schedule that re-verifies every 30 days during the active project period. The implementation is a lightweight version of the quarterly sweep described in other contexts - but scoped to your active project subs only, and with project-specific alerting.
// Monthly monitoring for active project subs
async function monitorActiveProjectSubs(projectId) {
const subs = await db.query(
`SELECT s.*, ps.trade_type, ps.project_state
FROM project_subs ps
JOIN subs s ON s.id = ps.sub_id
WHERE ps.project_id = $1
AND ps.status = 'awarded'`,
[projectId]
);
const alerts = [];
for (const sub of subs.rows) {
const current = await contractorVerifyApi.check({
licenseNumber: sub.license_number,
state: sub.project_state
});
if (current.status !== sub.last_verified_status) {
alerts.push({
subId: sub.id,
businessName: sub.business_name,
previousStatus: sub.last_verified_status,
currentStatus: current.status,
tradeType: sub.trade_type
});
await db.query(
`UPDATE project_subs
SET last_verified_status = $1, last_verified_at = NOW()
WHERE sub_id = $2 AND project_id = $3`,
[current.status, sub.id, projectId]
);
}
}
if (alerts.length > 0) {
await notifyProjectManager(projectId, alerts);
}
return alerts;
}
Handling a License Issue Mid-Project
When monitoring catches a license suspension or expiration during a project, you have a practical problem: the sub is on-site, work is ongoing, and pulling them off immediately has schedule and cost implications. The response protocol should be:
- Notify the project manager and owner's representative within 24 hours of the status change being detected
- Contact the sub directly to understand the cause - is this an administrative suspension (failed to renew, missed a fee) or something more serious (disciplinary action, fraud finding)?
- For administrative suspensions, set a 14-day cure period during which the sub must provide proof of reinstatement. Work continues under close supervision.
- For disciplinary suspensions, consult legal and insurance. Depending on contract language, you may need to remove them from the project or secure additional coverage for work performed during the suspension period.
- Document everything with timestamps. The API-generated monitoring record showing when the status changed and when you detected it is your evidence of due diligence.
Generating the Prequalification Report for Owners
Many owner contracts require GCs to provide documentation of their subcontractor prequalification process. An API-driven workflow makes this easy to generate - every verification is timestamped and stored, and a prequalification report is just a structured export of that data.
A complete prequalification report includes:
- Sub name, trade, and states verified
- License number, classification, and status at time of verification
- Bond amount verified
- Any flags raised and disposition (auto-cleared, reviewed by whom, on what date)
- API source and verification timestamp for each record
- Last re-verification date for subs currently on the project
This report demonstrates to owners that your prequalification was not a paper exercise - it was a real verification against authoritative sources, with timestamps that can be independently confirmed.
The Cost Comparison
Manual prequalification service
Per sub per year, billed by prequalification service companies like PICS or ISNetworld
API-based verification
Per sub per year at $0.08/lookup x 4 re-verifications annually (onboarding + 3 monitoring checks)
The cost difference is not the only consideration - prequalification service companies provide more than just license verification (they collect financial statements, safety records, and EMR rates). But for the license verification component specifically, the API approach is 300-1500x cheaper and returns results in seconds rather than days.
A GC that prequalifies 200 subs per year and uses API-based license verification for the initial screen saves approximately $60-100 of staff time per submission (estimated at $40/hour for 1.5-2.5 hours of manual verification work). At 200 subs, that is $12,000-$20,000 per year in recovered staff time, plus the avoided liability from catching the subs that would have slipped through with expired or suspended licenses.
Connecting to the Broader Verification Picture
Prequalification is one point in a contractor's lifecycle with your organization. License verification at the prequalification stage is most effective when it connects to a consistent verification policy across all touchpoints. For a comprehensive look at how state licensing structures affect what you need to check and where, the 2026 contractor license requirements overview is a useful reference. For the data infrastructure behind why consistent, reliable API responses across all 50 states is harder than it sounds, Building Scrapers for 50 State Licensing Boards explains the engineering challenges that affect data quality and latency.
Summary
GC prequalification has a license verification problem that is structurally identical to the problem in other domains: the information is publicly available, but the friction to access it manually is high enough that verification gets done inconsistently or not at all.
API-based verification does not replace prequalification - it replaces the manual license verification step within it. The result is a faster process (seconds instead of days for the license check), a more consistent process (every sub gets the same check, no sampling or shortcuts), better documentation (timestamped records tied to authoritative sources), and dramatically lower cost per verification.
The subs who are clean sail through automatically. The edge cases get appropriately flagged for human review. And the project files contain the verification records you need if questions arise about a sub's licensing status later in the project lifecycle.