Skip to content

Bulk license verification

Bulk license verification is checking a whole roster of professional licenses - hundreds or thousands at a time - against the state boards that issued them. Checking one license is trivial. Checking four thousand every month is a different problem, and the obvious approach is the one that gets you blocked.

By · Published · Updated

Why does scripting the board lookup forms fail?

The instinct is to script the board's public lookup form. It works for ten records and falls apart at a thousand, for reasons that are not really technical:

  • A state web application is in the request path, so each check is seconds rather than milliseconds. Four thousand sequential checks is hours of wall clock.
  • Government sites rate-limit and block automated traffic, and are entitled to. A monthly re-verification run looks exactly like abuse from their side.
  • Many boards have no public lookup form at all - they publish a downloadable file instead. There is nothing to script.
  • When the HTML changes, a scraper does not error. It stops matching and starts returning not found about people who are perfectly well licensed.

What approach scales to thousands of licenses?

Mirror what the boards already publish, and query the mirror. We hold 22,365,132 records across 31 states and 149 professions, refreshed on each source's own cadence. A batch then runs against an index instead of against a government website.

How batch jobs work

  • Submit up to 25,000 items in one job, depending on your plan.
  • Poll the job for progress. Jobs are asynchronous, not a blocking request.
  • Per-item results, each with its own status and provenance block, so one bad row does not fail the job.
  • Restart-safe. Items are saved one by one, and in-flight jobs resume after a deploy. A restart never silently drops work.

How do you run a batch job?

Three calls: submit the list, poll until it finishes, then page through the results. Each item names a profession, a two-letter state and the license number as the holder or the board writes it.

curl -X POST https://licenseverificationapi.com/api/v1/verify/batch \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"items": [
        {"profession": "nurse", "state": "TX", "license_number": "123456"},
        {"profession": "physician", "state": "FL", "license_number": "ME12345"}
      ]}'

The submit call answers 202 right away with a batch_id, a status_url and a results_url. The status URL reports completed_items out of total_items. Poll it every few seconds for a small job, or every minute for a large one.

Results come back in pages of up to 1,000 items, using offset and limit. Every item keeps its original position as idx, so you can join results back to your own rows without matching on license numbers. The full request and response schemas are in the API reference.

How big can a batch be?

The job size limit depends on your plan. Every item also counts against your monthly verification quota.

PlanItems per batch job
Free500
Developer2,500
Growth10,000
Scale and Enterprise25,000

A job that is larger than your plan allows, or larger than your remaining monthly quota, is refused before any work starts. You get an error that states the limit, not a half-finished job. For a handful of licenses, the synchronous bulk endpoint checks up to 50 in a single request and answers inline.

What does each result tell you?

Each item carries the same answer a single verification would give. The ones that need a human decision are worth handling explicitly:

  • active, expired, suspended, revoked, inactive, delinquent - the board's status, mapped to one vocabulary across every state.
  • multiple_matches - the same number belongs to more than one holder, which happens in states that number each license class separately. The candidates come back with their names, so you pick the right one instead of us guessing.
  • not_found - we hold that board's roster and the number is not in it. Check the number format before treating it as unlicensed.
  • not_supported - we do not hold that profession in that state yet. It says nothing about the person, and it is not billed. The batch size still has to fit your remaining quota when you submit it.
  • expired_unconfirmed and unknown - the roster we hold cannot settle the answer on its own: the expiry date passed after our latest copy, or the board used a status we do not map yet. Confirm these with the board.

The full list of statuses is in the API reference. Every answer also carries a provenance block: the source, its URL and the date of the roster it came from. That is what you keep for an audit trail.

Is a one-off batch enough?

Usually not. Most teams that ask about bulk actually want continuous. A batch run tells you the state of the world today; it does not tell you when something changes. Monitors re-verify on a schedule and push a signed webhook the day a status moves. That is usually what the bulk question was really about: not wanting to be the last to know a credential lapsed.

What will bulk not fix?

Coverage. If we do not hold a state, running it through a batch returns not_supported for every row in it. That is a statement about our data, not about your people. Check your actual states against the coverage matrix before planning a migration. Bulk extraction of the underlying roster data itself is not a permitted use - see the terms.

The free tier includes 500 verifications a month and every data feature, which is enough to run a real sample of your roster before committing. API reference · For staffing agencies · Pricing