# North Carolina Contractor License Lookup Scraper (`automation-lab/north-carolina-contractor-license-lookup-scraper`) Actor

Search public NCLBGC contractor records by license, qualifier, company, person, classification, phone, or address. Export status, validity, dates, limitation, classifications, address, and phone.

- **URL**: https://apify.com/automation-lab/north-carolina-contractor-license-lookup-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## North Carolina Contractor License Lookup Scraper

Search the official public North Carolina Licensing Board for General Contractors (NCLBGC) registry and export structured contractor, license, and qualifier records.

Use it to verify a license before onboarding a contractor, create a territory list, or monitor public status and expiration data without manually opening each result.

The Actor uses the anonymous NCLBGC search and detail pages.
It does not require a board account, browser, CAPTCHA solver, or proxy.

### What does this Actor do?

The Actor submits one or more searches to the public NCLBGC portal.

For every unique match, it opens the public detail record and returns:

- contractor, company, or qualifier name;
- license or account number;
- account type;
- active and validity signals;
- public status;
- public address and phone;
- first-issued and expiration dates;
- license limitation;
- active classifications;
- source and detail references.

Blank fields from the board remain `null` rather than being guessed.

### Who is it for?

**Procurement and compliance teams** can verify contractors before onboarding or renewal.

**General contractors and property teams** can check subcontractor license status.

**Insurance and supplier teams** can build state-specific contractor prospect lists.

**Construction sales teams** can search by company, classification, or geography.

**Analysts** can schedule recurring searches and compare snapshots for public status or expiration changes.

### Why use this NCLBGC lookup?

- Uses the official public NCLBGC source.
- Supports the fields exposed by the board's advanced search form.
- Fetches detail records instead of returning only names from the result table.
- Deduplicates overlapping searches by account number.
- Stops at a user-defined record limit.
- Uses conservative HTTP concurrency.
- Produces JSON, CSV, Excel, XML, RSS, or HTML through Apify datasets.
- Works with schedules, webhooks, API clients, and MCP tools.

The Actor reports what the public registry displayed at extraction time.
It is not a substitute for legal advice or a certified license document.

### What data can you extract?

| Field | Meaning |
|---|---|
| `name` | Contractor, company, instructor, or qualifier name |
| `accountNumber` | NCLBGC license/account number |
| `accountType` | Registry account type |
| `isActive` | Search row was not marked “License Not Active” |
| `isValid` | Validity derived from the detailed status, or `null` if inconclusive |
| `status` | Full normalized public status |
| `address` | Public contact address, or `null` |
| `phone` | Public phone, or `null` |
| `firstIssuedDate` | First-issued date shown by NCLBGC |
| `expirationDate` | Expiration date shown by NCLBGC, or `null` |
| `limitation` | Limited, Intermediate, Unlimited, or another displayed limitation |
| `classifications` | Active classifications listed on the detail page |
| `search` | Normalized filters that produced the record |
| `sourceUrl` | Official NCLBGC search page |
| `detailUrl` | Official detail reference used for extraction |
| `scrapedAt` | ISO 8601 extraction timestamp |

### How to search North Carolina contractor licenses

1. Open the Actor input page.
2. Add one or more objects to **Searches**.
3. Put at least one filter in every search object.
4. Optionally enable **Active licenses only**.
5. Choose a maximum number of records.
6. Keep detail concurrency at the default unless you have a specific reason to change it.
7. Click **Start**.
8. Open the **License records** dataset view.
9. Export the records or connect them to your workflow.

A license-number lookup is the fastest way to verify one known record:

```json
{
  "searches": [
    { "licenseNumber": "01060" }
  ],
  "maxItems": 5
}
```

### Input parameters

#### `searches`

Required array with 1–50 search objects.
Fields within one object are combined by NCLBGC.
Different objects are separate searches.

Supported fields:

- `classificationId`
- `licenseNumber`
- `qualifierNumber`
- `companyName`
- `firstName`
- `lastName`
- `phoneNumber`
- `streetAddress`
- `postalCode`
- `city`
- `stateCode`
- `includeSoundex`

Common NCLBGC classification IDs include `27` for Building and `28` for Residential.
The board controls this identifier list and can change it.

#### `activeOnly`

When `true`, the Actor skips search rows marked **License Not Active** before requesting their details.
Default: `false`.

#### `maxItems`

Maximum unique records across all searches.
Default: `100`.
Maximum: `10,000`.

#### `maxConcurrency`

Number of simultaneous detail requests.
Default: `5`.
Allowed range: `1`–`10`.

### Example output

This abbreviated real-source shape shows how blank NCLBGC values are represented:

```json
{
  "name": "J. Addison Smith",
  "accountNumber": "L.01060",
  "accountType": "License",
  "isActive": false,
  "isValid": false,
  "status": "Archived - License Not Valid",
  "address": "1201 Asheboro Street Greensboro, NC",
  "phone": null,
  "firstIssuedDate": "02/25/1936",
  "expirationDate": null,
  "limitation": "Intermediate",
  "classifications": ["Building"],
  "sourceUrl": "https://portal.nclbgc.org/Public/Search",
  "scrapedAt": "2026-01-15T12:00:00.000Z"
}
```

The default dataset contains one object per unique account.

### How much does it cost to look up North Carolina contractor licenses?

Pricing has two events:

- **Start:** $0.005 once per run.
- **Item extracted:** tiered from $0.000041071 per record on FREE to $0.00001 on DIAMOND.

At the FREE tier, excluding Apify platform plan allowances:

- 1 returned record costs about **$0.00504** including the start event;
- 100 returned records cost about **$0.0091** including the start event;
- 1,000 returned records cost about **$0.0461** including the start event.

Empty searches incur only the start event.
Duplicate rows and records filtered out by `activeOnly` are not charged as extracted items.
Apify shows the applicable tier and maximum charge before a run.

### Monitoring and automation workflows

#### Contractor onboarding

Search by license number and store the returned status, expiration, limitation, and classifications with the vendor record.

#### Renewal monitoring

Schedule the same set of license searches weekly or monthly.
Send the dataset to a webhook and compare `status` or `expirationDate` with the previous snapshot.

#### Territory lead building

Combine a classification with city, ZIP code, or address filters.
Export active records to CSV or a CRM.

#### Qualifier research

Use `qualifierNumber`, first name, or last name when the board exposes a qualifier relationship through its public search.

### Limits and failure behavior

The source is a live public government portal.
Availability and HTML structure are controlled by NCLBGC.

The Actor:

- retries temporary network errors and HTTP 429/5xx responses up to three times;
- fails rather than silently returning zero rows when the response shape is unrecognized;
- treats a recognized no-result search as a successful zero-item result;
- rejects empty search objects;
- rejects malformed state, ZIP, license, and qualifier values;
- caps detail concurrency at 10;
- does not bypass access controls.

Searches that match many records can take longer because every accepted row receives a detail request.
Use narrow filters and a practical `maxItems` value.

### Tips for better results

- Remove an `L.` prefix or leave it in; the Actor normalizes either form.
- Use the exact numeric license number for verification.
- Search company names without corporate suffixes if an exact query is too narrow.
- Enable `includeSoundex` for person-name spelling variants.
- Use `activeOnly` to avoid detail requests for visibly inactive search rows.
- Combine city with classification for focused lists.
- Use multiple search objects when you need OR-style searches.
- Expect phone or expiration values to be `null` when NCLBGC leaves them blank.

### Use the Actor through the API

Replace `YOUR_APIFY_TOKEN` with your token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~north-carolina-contractor-license-lookup-scraper/runs?token=YOUR_APIFY_TOKEN&waitForFinish=300" \
  -H "Content-Type: application/json" \
  -d '{"searches":[{"companyName":"Smith"}],"activeOnly":true,"maxItems":20}'
```

Fetch items from the `defaultDatasetId` returned by the run.

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor(
  'automation-lab/north-carolina-contractor-license-lookup-scraper'
).call({
  searches: [{ licenseNumber: '01060' }],
  maxItems: 5,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor(
    "automation-lab/north-carolina-contractor-license-lookup-scraper"
).call(run_input={
    "searches": [{"classificationId": "28", "city": "Raleigh"}],
    "activeOnly": True,
    "maxItems": 100,
})

items = client.dataset(run["defaultDatasetId"]).list_items().items
print(items)
```

### Use with MCP and AI agents

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/north-carolina-contractor-license-lookup-scraper"
```

#### Claude Desktop

Add this server to the `mcpServers` object in your Claude Desktop configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/north-carolina-contractor-license-lookup-scraper"
    }
  }
}
```

#### Cursor

Open Cursor MCP settings and add the same `apify` server JSON above as a remote HTTP MCP server.

#### VS Code

Open VS Code MCP settings, create an HTTP server named `apify`, and use the same `https://mcp.apify.com?tools=automation-lab/north-carolina-contractor-license-lookup-scraper` URL.

Example prompts:

- “Verify NCLBGC license 01060 and summarize its public status.”
- “Find up to 50 active Residential contractor records in Raleigh.”
- “Export active Smith contractor records with classifications and expiration dates.”

### Integrations

Use Apify integrations to send results to:

- Google Sheets;
- webhooks;
- Make;
- Zapier;
- Slack;
- a database or data warehouse;
- an internal procurement or CRM system.

For monitoring, schedule the Actor and use a webhook after successful runs.
Store `accountNumber` as the stable comparison key.

### Responsible use and legality

This Actor accesses public information displayed by NCLBGC.
You are responsible for your use of the data and for complying with applicable laws, source terms, privacy obligations, and anti-discrimination rules.

Do not use public license data for harassment, spam, identity theft, or unlawful eligibility decisions.
Minimize retention of contact information and secure exported datasets.

Always confirm high-stakes decisions with the official board or an authoritative certificate.

### Troubleshooting

#### Why did the run return zero records?

The board may have no match for the combined filters.
Try a license number without punctuation, remove a corporate suffix, or split combined filters into separate search objects.

#### Why is a field `null`?

NCLBGC sometimes leaves phone, expiration, address, or other detail fields blank.
The Actor preserves that absence instead of inventing a value.

#### Why did the Actor fail instead of returning an empty dataset?

The Actor fails when NCLBGC returns an unexpected page, persistent server error, or changed markup.
This protects downstream workflows from treating a blocked or broken response as a genuine no-result search.

#### Is `isActive` the same as `isValid`?

No.
`isActive` comes from the search-row marker.
`isValid` is derived from the detailed status.
Use `status` for the full source wording.

### FAQ

#### Does it need an NCLBGC login?

No.
It uses the anonymous public search.

#### Does it use a browser or proxy?

No.
The current route is direct HTTP with a session cookie and Cheerio parsing.

#### Can it download every North Carolina license?

The product is a filtered lookup tool, not a guaranteed statewide bulk export.
Use supported search filters and respect the 10,000-item run limit.

#### Can it monitor changes automatically?

Yes.
Schedule repeated searches and compare datasets by `accountNumber`.
The Actor itself does not maintain historical state.

#### Is the data real time?

It reflects what the public NCLBGC portal returned during the run.
The board controls update timing.

### Related automation-lab Actors

- [Oregon Contractor License Lookup Scraper](https://apify.com/automation-lab/oregon-contractor-license-lookup-scraper)
- [Washington Contractor License Lookup Scraper](https://apify.com/automation-lab/washington-contractor-license-lookup-scraper)
- [Virginia DPOR Contractor License Leads Scraper](https://apify.com/automation-lab/virginia-dpor-contractor-license-leads-scraper)

Use separate state Actors when your compliance or lead workflow spans multiple licensing authorities.

### Support

If a run fails, include the run URL and a redacted version of the input.
Do not post private credentials or non-public personal data.

Source fields and classifications can change when NCLBGC updates its portal.

# Actor input Schema

## `searches` (type: `array`):

One or more searches. Add at least one filter to each object; fields within one object are combined.

## `activeOnly` (type: `boolean`):

Skip search rows marked License Not Active before fetching their details.

## `maxItems` (type: `integer`):

Stop after this many unique contractor or qualifier records across all searches.

## `maxConcurrency` (type: `integer`):

Concurrent NCLBGC detail requests. Keep the default for conservative public-registry access.

## Actor input object example

```json
{
  "searches": [
    {
      "companyName": "Smith"
    }
  ],
  "activeOnly": true,
  "maxItems": 20,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Default dataset containing all unique public NCLBGC records returned by the searches.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searches": [
        {
            "companyName": "Smith"
        }
    ],
    "activeOnly": true,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/north-carolina-contractor-license-lookup-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searches": [{ "companyName": "Smith" }],
    "activeOnly": True,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/north-carolina-contractor-license-lookup-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searches": [
    {
      "companyName": "Smith"
    }
  ],
  "activeOnly": true,
  "maxItems": 20
}' |
apify call automation-lab/north-carolina-contractor-license-lookup-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/north-carolina-contractor-license-lookup-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/eurVwX6llk7cvynbc/builds/hzFwrXh5teOxsnUce/openapi.json
