NAIC SBS Insurance Producer Scraper
Pricing
Pay per usage
NAIC SBS Insurance Producer Scraper
Look up and bulk-export insurance producer and licensee records from the NAIC State Based Systems public lookup. Covers all 34 SBS jurisdictions, individuals and business entities, and all license statuses including expired, revoked and cancelled.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Gufran
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 hours ago
Last modified
Categories
Share
Look up insurance producers, agents and agencies licensed across the NAIC State Based Systems (SBS) — the official registry used by 34 US states, the District of Columbia, Guam, Puerto Rico and the US Virgin Islands.
Three jobs, one Actor:
| Job | Mode | Requests |
|---|---|---|
| Verify one licence — "is this agent actually licensed, and for what?" | targeted with an NPN or licence number | 1 |
| Find a producer — "every S-* producer in New Jersey" | targeted with a name | 1–15 |
| Bulk export — "every producer and agency in Delaware" | enumerate | hundreds |
Coverage
| Jurisdictions | All 34 SBS participants |
| Individuals | Yes — producers, agents, brokers |
| Business entities | Yes — agencies, brokerages |
| Licence statuses | All 14 — active, expired, revoked, cancelled, suspended, denied and more |
| Full detail | Optional — emails, websites, LOA qualifications, CE compliance, appointments |
The problem this Actor solves
Most NAIC scrapers quietly lose data. Here's why, and what this one does instead.
The NAIC search API returns a bare JSON array with no pagination and a hard cap of 25 rows per query. It just truncates. NAIC's own website can't tell you the total either — it shows a warning that 25 results were returned, and that's all.
The usual workaround, searching single letters, still breaks. Last name S, first name J in New Jersey returns exactly 25 rows — and rows 26 onward are gone, with no error.
This Actor treats a full page as "there is more behind this". It splits that query into finer partitions, re-runs each one, and repeats until nothing comes back truncated:
"S" -> 25 rows (truncated) -> split into "SA" "SB" "SC" ..."SB" -> 8 rows (complete) -> done"SA" -> 25 rows (truncated) -> split further
When a partition is still capped at maximum depth and cannot be split again, the Actor writes that exact prefix to a gaps list in its key-value store. You always know what was missed. Nothing disappears quietly.
Quick start
1. Verify one producer by NPN — 1 request
The fastest path. An NPN is a unique national identifier.
{ "jurisdictions": ["NJ"], "npn": 19623468, "enrichDetail": true }
2. Find producers by name
{ "jurisdictions": ["NJ"], "searchMode": "targeted", "lastName": "Smith" }
3. Export a whole jurisdiction
{"jurisdictions": ["DE"],"searchMode": "enumerate","entityTypes": ["IND", "ENT"],"maxResults": 15000}
4. Everyone in one state who is not currently licensed
Compliance and lead-gen use case — licences that lapsed, were revoked, or were surrendered.
{"jurisdictions": ["NJ"],"searchMode": "targeted","lastName": "A","licenseStatuses": ["CA", "REV", "EX", "SUS"]}
Choosing a mode
Use targeted (default) whenever you have a concrete filter — a name, an NPN, a licence number, a city, a zipcode. It is 1 to 15 requests and finishes in seconds.
Use enumerate only when you want a whole population. It walks the name space letter by letter, subdividing wherever it hits the cap. Expect hundreds or thousands of requests, and a runtime measured in minutes.
| You want | Mode |
|---|---|
| One specific person | targeted |
| Everyone matching a name prefix | targeted with lastName |
| Every licence in a state | enumerate |
| Only revoked/expired licences | targeted + licenseStatuses |
| Producers in one city | targeted + businessCity |
How a run actually executes
Understanding this helps you pick sensible limits.
targeted — one API call per (jurisdiction × entity type × licence status). If you supply an NPN or licence number, that collapses to a single call regardless of how many statuses exist, because those identifiers are unique.
enumerate — starts with 26 partitions, one per last-name initial, per jurisdiction × entity type × status. Any partition returning a full 25-row page is subdivided and re-queued. The crawl converges until nothing truncates.
While it runs, the status line updates every 50 queries:
250 queries done - 120 unique licensees collected - 0 unresolved gap(s).
Duplicate licencees are detected and never charged twice, and maxResults is a hard cap — the Actor stops producing the moment it's reached.
Measured behaviour
Real runs of this Actor, so you can size your expectations:
| Run | Records | Wall time | Platform cost |
|---|---|---|---|
| NPN lookup + full detail | 1 | ~10 s | $0.004 |
targeted name, 10 records | 10 | ~15 s | $0.003 |
enumerate NJ, 500 records | 500 | ~60 s | $0.024 |
Cost per record works out to roughly $0.00005–$0.00009, dominated by request-queue writes from subdivision rather than compute.
Input reference
Scope
| Field | Type | Default | What it does |
|---|---|---|---|
jurisdictions | array | ["NJ"] | Two-letter SBS codes, or ALL for all 34. |
searchMode | string | targeted | targeted = answer a question. enumerate = collect a population. |
entityTypes | array | ["IND"] | IND individual producers, ENT agencies and brokerages. Add both for full coverage. |
licenseStatuses | array | [] = all | Leave empty to include every status. A active, B approved-not-active, CA cancelled, EX expired, IN inactive, REV revoked, SUS suspended, DEN denied, and others. Codes vary slightly by state. |
licenseTypeCodes | array | [] = auto | Usually leave empty — the Actor detects each state's producer code. Common: PRO, PAJ public adjuster, TPA. |
loaTypes | array | [] = all | Optional filter. LLA accident & health, LLL life, LLC casualty, LLP property. |
Search criteria — targeted mode only
Leave every field in this group empty when using enumerate.
| Field | Type | What it does |
|---|---|---|
lastName | string | Matches from the start of the name. Prefix match, not exact. |
firstName | string | Prefix match. |
dbaLastName | string | Searches assumed / trading names rather than the legal name. |
npn | integer | Exact National Producer Number. Fastest single lookup. |
licenseNumber | integer | Exact licence number. |
fein | string | Federal Employer Identification Number. |
businessCity | string | City of the business address. |
businessState | string | State or province of the business address. |
businessZipcode | string | Zipcode of the business address. |
mailingCounty | string | County of the mailing address. |
residentLicense | string | Yes for residents only, No for non-residents, empty for both. |
Output
| Field | Type | Default | What it does |
|---|---|---|---|
enrichDetail | boolean | false | Adds ~8 extra requests per record and returns emails, websites, a structured address, per-line qualification and exam dates, CE compliance, appointments, branch offices and the NAIC internal licence id. Off by default because it multiplies cost. |
maxResults | integer | 1000 | Hard cap on unique licencees. The run stops the moment it's reached. |
maxRequests | integer | 5000 | Hard ceiling on upstream API calls. In enumerate mode you can hit this before maxResults, because subdivision costs requests. |
concurrency | integer | 8 | Parallel requests. Start here; raise only if a run feels slow. |
Proxy
| Field | Type | Default | What it does |
|---|---|---|---|
useProxy | boolean | true | Routes through Apify Proxy. Recommended for bulk runs. Turn it off for a one-off lookup to shave the cost — direct requests work unless NAIC answers 403. |
proxyConfiguration | object | { "useApifyProxy": true } | Advanced proxy settings. The group is deliberately left unset so Apify picks the cheapest one your plan includes. Don't pin a group you aren't entitled to — naming an unavailable one (e.g. SHARED_DATACENTER, a paid add-on) makes the run fail immediately. |
proxyCountry | string | US | Proxy exit country. |
resume | boolean | true | Reuse the checkpoint so an interrupted run continues instead of re-charging for records already collected. |
Proxy cost, measured on a 10-record run: $0.0026 with the default, $0.0025 with the proxy off, $0.0047 if you force residential. Residential is billed at roughly 4x the datacenter rate and this API does not need it — reach for it only if you hit repeated 403s.
Output
One dataset item per unique licensee. Empty values are "", never missing.
{"licenseNumber": "3003526706","naicLicenseId": null,"name": "AACH, CAYLEIGH JACLYN","lastName": "AACH","firstName": "CAYLEIGH","middleName": "JACLYN","licenseType": "Insurance Producer-Active","licenseTypeCode": "PRO","licenseStatus": "Active","licenseEffectiveDate": "02/26/2025","licenseExpirationDate": "04/30/2027","linesOfAuthority": [{ "line": "Accident & Health or Sickness", "grantedOn": "02/26/2025" },{ "line": "Casualty", "grantedOn": "02/26/2025" },{ "line": "Life", "grantedOn": "02/26/2025" },{ "line": "Property", "grantedOn": "02/26/2025" }],"naicProducerNumber": "19623468","residentLicense": "No","businessAddress": "BOYERTOWN, PA 19512","businessAddressCity": "BOYERTOWN","businessAddressState": "PA","businessAddressZipcode": "19512","businessPhone": "(610) 573-7339","businessPhoneRaw": null,"fein": "","dbaName": "","designatedHomeState": "","entityType": "IND","jurisdiction": "NJ","jurisdictionName": "New Jersey","email": null,"sourceUrl": "https://external-lookup-web.prod.naic.org/solar-external-lookup/lookup/licensee/summary/3003526706?jurisdiction=NJ&entityType=IND&licenseType=PRO","licenseManagerUrl": "https://external-lookup-web.prod.naic.org/solar-external-lookup/license-manager?entityType=IND&licenseNumber=3003526706&lastName=AACH&jurisdiction=NJ","detail": null,"scrapedAt": "2026-10-02T15:23:50.531Z"}
| Field | Notes |
|---|---|
licenseNumber | The state licence number as issued. |
naicLicenseId | NAIC's internal id. Only set when enrichDetail is on. |
name / lastName / firstName / middleName | Raw name plus parsed components. |
licenseType | Combined type and status, e.g. Insurance Producer-Active. |
licenseStatus | Just the status: Active, Expired, Revoked, Cancelled… |
linesOfAuthority | Every line held, each with the date it was granted. |
naicProducerNumber | The NPN. |
residentLicense | Yes or No. |
businessAddress* | Pre-split into city / state / zipcode, so you don't have to parse it. |
businessPhone | Formatted (XXX) XXX-XXXX. |
email | Only with enrichDetail. |
sourceUrl | Direct link to the official NAIC licensee page. |
licenseManagerUrl | Direct link to the state's licence manager portal. |
detail | Only with enrichDetail. Contains licenses, dbaNames, per-line linesOfAuthority (with qualification, schoolCode, examCertDate, lineStatus), emails, phones, urls, demographics, continuingEducation, appointments, branchOffices, drlp, businessEntityAffiliations, and a detailErrors list if any sub-resource failed. |
Resuming an interrupted run
Every run writes a CHECKPOINT_naic-sbs record to the key-value store holding the licence numbers already collected, the partitions already completed, and any unresolved gaps.
Re-running the same input picks up where it left off. Completed partitions aren't re-run and already-collected licencees aren't charged again, so a resumed run only seeds what's missing. Keep resume: true (the default) if you expect long enumerations to get interrupted.
Troubleshooting
No results for a state I expected data from. California, Texas, Florida, New York, Pennsylvania, Ohio, Georgia, Michigan and Washington license through their own separate systems and aren't part of NAIC SBS. They aren't accepted as jurisdiction codes, and the input schema says so.
"The maximum number of results, 25, were returned" warnings. Expected in targeted mode — it means that single query is genuinely larger than NAIC will return at once. Narrow the filter, or switch to enumerate, which subdivides and collects the full set.
Worried about missed records? Read gaps in the CHECKPOINT_naic-sbs key-value store record. If a prefix is listed there, those licencees couldn't be enumerated. An empty list means full coverage.
Frequent 403s or timeouts. Lower concurrency to 4, make sure useProxy is on, and consider a different proxyCountry.
Run stopped early. Check maxRequests. In enumerate mode, subdivision consumes requests quickly, so this ceiling can be reached before maxResults.
Data source and attribution
Data comes from the public NAIC State Based Systems external lookup API at api.prod.naic.org. This Actor is an independent tool and is not affiliated with or endorsed by the NAIC. State licence records are public regulatory filings — you are responsible for your own compliance with applicable data-protection and fair-practice obligations.
Changelog
See ./CHANGELOG.md.