Skip to content
PestPin

Developers

The pest control license API

Pest control is licensed by state agriculture and environment departments, not by contractor boards, so it is missing from the contractor license files most vendors sell. We process those agencies' own records for 33 states and this API returns them. Three read-only endpoints, JSON, no key needed today.

Start here

Paste this into a terminal. Nothing to sign up for.

curl -s "https://pestpin.com/api/v1/licenses/lookup?state=TX&license_number=0565822"

The three endpoints

GET /api/v1/licenses/lookup

Returns an array. License numbers are shared between businesses and repeat across states, so one match and four matches are the same shape. A number we hold no record for returns 200 with an empty array, never 404. This is the only endpoint that returns records from states which license the individual operator rather than the firm; those carry license_subject 'individual'.

  • state required. Two letters.
  • license_number required. 1 to 40 characters after trimming.
curl -s "https://pestpin.com/api/v1/licenses/lookup?state=TX&license_number=0565822"

GET /api/v1/licenses/search

Name match, 25 records per page. Pass a state: a bare name search across every covered state is a worse answer, not a better one. Records from states that license the individual operator rather than the firm are not returned here; they are counted in withheld, the notes say why, and the license-number lookup returns them. total counts only the records you can page through, so total never includes a withheld record.

  • name required. 2 to 100 characters.
  • state optional. Two letters. Strongly recommended.
  • page optional. 1 to 2000.
curl -s "https://pestpin.com/api/v1/licenses/search?state=FL&name=arrow"

GET /api/v1/licenses/city

The businesses PestPin lists for one city and pest type, 50 per page. total counts only the records you can page through. listed_on_city_page is the same live count the public city page prints, which counts every matched business including any this endpoint withholds, so a caller and a reader are never told different numbers for the same thing. Records from states that license the individual operator rather than the firm are not returned here; they are counted in withheld, the notes say why, and the license-number lookup returns them.

  • city required. 2 to 60 characters. City name or city slug.
  • state required. Two letters.
  • pest optional. Pest slug or common name. Defaults to general pest control.
  • page optional. 1 to 40.
curl -s "https://pestpin.com/api/v1/licenses/city?city=houston&state=TX&pest=termite-control"

Search returns 25 records per page and the city listing returns 50. Page on total: it counts only the records you can actually reach in matches, so it will never hand you a page that comes back empty. Anything an endpoint holds back is counted separately in withheld, with the reason in notes. The city listing also returns listed_on_city_page, the count our public city page prints, which counts every matched business including any this endpoint withholds.

The full machine-readable spec is at /api/v1/openapi.json.

What a record looks like

{
  "matches": [
    {
      "name": "Example Pest Control LLC",
      "name_source": "regulator",
      "license_subject": "business",
      "license_number_subject": "business",
      "license_number": "0565822",
      "license_state": "TX",
      "license_expires_on": "2027-06-30",
      "city": "Houston, TX",
      "record_status": "matched to a state license record that was marked active at our most recent check of the state source",
      "source_url": "https://texasagriculture.gov/...",
      "source_checked_on": "2026-09-07",
      "in_directory_since": "2026-07-14",
      "pestpin_url": "https://pestpin.com/companies/example-pest-control-llc",
      "match_type": "exact"
    }
  ],
  "total": 1,
  "withheld": 0,
  "coverage": "covered",
  "note": "..."
}
FieldWhat it is
nameBusiness name on the record we matched.
name_sourceregulator when the name is the state's, owner when the business has claimed its listing and typed its own.
license_subjectbusiness where the state licenses the firm, individual where it licenses the person. On an individual record the name is a private person and not a company, and it does not say who employs them. Only the license-number lookup returns individual records.
license_number_subjectbusiness where license_number is the firm's own licence. applicator where the state publishes no firm number and the number carried is a licensed individual's credential at that firm, so the official lookup returns that person's record and not the company's. South Carolina is applicator: Clemson DPR publishes only its commercial applicator report.
license_numberLicense number as the regulator published it. Numbers are shared between businesses and repeat across states. Read license_number_subject before describing whose licence it is.
license_stateTwo-letter state of the license record.
license_expires_onExpiry exactly as published, or null where the state publishes none. Never parsed into a judgment.
cityCity and state of the record location, which is where the regulator placed the record.
record_statusWhat we matched, in a sentence. There is no boolean status field and there will not be one.
source_urlThe regulator's own page for this record. Required on every record.
source_checked_onThe date we last worked through the state's source, as a calendar date.
in_directory_sinceThe date this record first cleared our checks.
pestpin_urlThe public PestPin page for this record.
match_typeexact or normalized on a license-number lookup, name on a name search, city on a city listing, phone on a phone-number lookup. A phone match is always exact on ten digits; there is no partial phone match.

Five things that will surprise you

  • A miss is a 200, not a 404. No match returns an empty array. A 404 would read as "this company is not licensed", and that is not something we can tell you. Coverage is partial, lawn and agricultural records are excluded on purpose, and name spellings vary.
  • A lookup returns an array. License numbers are shared between businesses and repeat across states. One match and four matches are the same shape.
  • There is no boolean status. No is_active, no verified, no score. We publish what a state roster said, the date we read it, and a link to the source. A boolean would be a claim about today made from yesterday's roster.
  • In one state the license belongs to a person, not a company. North Carolina issues its structural pest control license to the individual operator, and its public record carries no firm name at all. Those records are named private people, so the name search and the city listing do not return them. They are counted in withheld, never in total, with the reason in notes. The license-number lookup still returns them, marked license_subject: "individual". Never display one as a company.
  • In one state the number is an employee's, not the company's. South Carolina publishes only its commercial applicator report, which lists people and the company each one works for. There is a separate business license number, but the state prints it in no record we can read, so a South Carolina record carries a licensed applicator's number instead, marked license_number_subject: "applicator". The company name is still the company's, and these records are returned everywhere. Never label that number as the company's own license.
  • A database outage is a 503 with no matches key. An empty list during an outage would tell you a licensed company does not exist. Treat a 503 as "ask again shortly", never as an answer.

Coverage, and what a zero means

Every response carries a coverage field, because zero matches means three different things:

  • covered: we process this state's records and hold nothing matching your query.
  • lookup_only: the state has a real licensing agency and an official search, and we hold no records for it. The response carries that agency's own lookup URL.
  • not_covered: neither. See /verify for the regulator we have on file for each state.

PestPin matches official state license records; it does not verify insurance, work quality, or availability. No match is not proof a company is unlicensed (33-state coverage, name spellings vary). Verify any record at the official source link.

Limits and caching

Today the endpoints are open and unkeyed, limited to 6 requests a minute and 50 a day per address, with a shared daily ceiling across all callers. Every successful response ships public, s-maxage=3600, stale-while-revalidate=86400, so a client polling the same license number is answered at the edge. One hour is honest: the records behind it change at most once a day, when our refresh runs.

Cross-origin GET is allowed from any origin, so you can call this from a browser.

Attribution

Every response carries the header X-PestPin-Attribution: required and an attribution string. While the API is free, any screen that displays a record must show "License record via PestPin" linked to that record's pestpin_url. Paid plans will drop the visible PestPin credit. The regulator source_url stays visible on every plan, for the reason in the next section.

Terms, in short

The full text is in our terms. The seven points that matter to a developer:

  1. Query and display records inside your own product. Do not resell, sublicense, or republish the data as a dataset, and do not use the API to rebuild a competing license database. For bulk, see license data.
  2. If a consumer sees a record, they must also see the regulator source link. Do not describe a record as certification, verification, approval, or endorsement, by us or by any government body. A state attorney general has already fined a national home services company over a badge like that.
  3. Do not present a derived "active", "valid" or "licensed today" conclusion as coming from PestPin. We publish what a roster said on a date, and the date ships with it.
  4. Cache a record for up to 30 days, then re-check it. A stale cached "licensed" is exactly the harm this data exists to prevent.
  5. Show the attribution described above while you are on the free tier.
  6. We may suspend access for a terms breach or for abuse.
  7. No warranty of coverage or currency. 33 states, spellings vary, coverage changes, and a miss is not proof of anything.

Sole proprietors appear in these rosters under their own names. A person or business who wants a listing removed uses our removal request, and a removal takes effect in the API on the same cycle it takes effect on the site.

Plans

PlanPriceLookupsStatus
Free$0100 a monthOpen now, no key needed
Starter$49 a month5,000 a monthComing, not on sale yet
Growth$199 a month25,000 a monthComing, not on sale yet

There is no key to issue yet, so nothing here is behind a signup. If you are building against this and want to be told the day keys and paid plans open, email hello@pestpin.com and say what you are building. We answer.

Want the whole file rather than per-lookup calls? That is a separate product. See license data.