Notion
Contractor License Expiration Monitor & Alerts (Notion)
Watch a book of contractors across 8 states and get expiry, lapse and revocation alerts written straight into your own Notion - the Actor never sees your Notion token.
Verification
Output fields
Authoritative field set from the dataset schema.
| Field | Type |
|---|---|
requested_company | string|null |
requested_state | string|null |
requested_license_number | string|null |
alert | string|null |
alert_reason | string|null |
license_number | string|null |
business_name | string|null |
license_status | string|null |
license_type | string|null |
expiration_date | string|null |
days_to_expiry | integer|null |
previous_status | string|null |
city | string|null |
authority | string|null |
checked_at | string|null |
fetch_error | string|null |
source_url | string|null |
scraped_at | string|null |
Sample row
{
"requested_company": "ARIZA, JOHN MARIO",
"requested_state": "FL",
"requested_license_number": "CGC1514724",
"alert": "status_change",
"alert_reason": "license status is 'Null and Void'",
"license_number": "CGC1514724",
"business_name": "ARIZA, JOHN MARIO",
"license_status": "Null and Void",
"license_type": "Certified General Contractor",
"expiration_date": "08/31/2020",
"days_to_expiry": -2166,
"previous_status": null,
"authority": "DBPR",
"checked_at": "2026-08-06T17:11:02+00:00",
"fetch_error": null,
"source_url": "https://www.myfloridalicense.com/wl11.asp"
} Inputs
| Field | Type | Req | Default | Description |
|---|---|---|---|---|
watchlist | array | no | [{"company":"ABC Roofing","state":"CA","license_number":"1076169"},{"company":"Example Builders","state":"WA","license_number":""}] | The contractors to watch. One object per contractor: {"company": "ABC Roofing LLC", "state": "CA", "license_number": "123456"}. Include the license number whenever you have it — it is the exact search axis, so a licence found by number is never a near-miss. A name-only row is matched on the full normalized business name (case, punctuation and LLC/Inc suffixes are ignored); if the name resolves to more than one licence the row comes back as 'check failed — manual review' rather than guessing. Covered states: CA, CT, FL, NM, OR, SC, TX, WA. Any other state is returned as 'unverifiable — state not covered', never as unlicensed. |
connector | string | no | — | Where to write the compliance table and the alert digest. This Actor never sees your Notion or Slack token — Apify injects your credentials server-side and only lets this Actor call the page search/create/update tools declared in its schema. Notion is the recommended target: Apify manages the OAuth, so authorizing it is a single click from this form. Slack works too, but Slack requires YOU to register your own Slack OAuth app first — that is Slack's requirement, not ours, and it takes about ten minutes. Leave this empty to run dataset-only with no delivery. |
pageTitle | string | no | "Contractor License Compliance Monitor" | The Actor upserts ONE page with this title: it searches for the page, updates it in place when it exists, and creates it otherwise — so repeated runs never duplicate rows. Change this if you monitor more than one book of contractors and want each in its own page. |
warnDays | integer | no | 30 | A licence expiring within this many days is reported as EXPIRING. Renewal windows are typically 30-60 days, so 30 gives you time to chase a renewal before the licence actually lapses. |
alertOn | array | no | ["expiring","expired","status_change"] | Which tiers go in the alert digest. Valid values: 'expiring', 'expired', 'status_change'. Leave empty for all three. Note that 'check_failed' and 'unverifiable_state_not_covered' are ALWAYS reported no matter what you set here — they describe the check rather than the contractor, and hiding them would turn a broken check into a false all-clear. |
Pricing
Pay per result (license-checked) — no charge for empty runs.
Example: 100 licences checked against a state board at $0.004/result ≈ $0.40.
FAQ
Which states does it cover?
Eight: California (CSLB), Connecticut (DCP), Florida (DBPR), New Mexico (CID), Oregon (CCB), South Carolina (LLR), Texas (TDLR) and Washington (L&I). Any other state comes back as 'unverifiable - state not covered'. That row is never dropped and never reported as unlicensed - it means we did not check, not that the contractor has no licence.
Does 'not found' mean the contractor is unlicensed?
No, and this is the most important line here. It means the state registry returned no match for what you searched. A typo, a DBA that differs from the licensed entity name, or a licence held under a parent company will all produce 'not found'. Treat it as 'go and look', never as evidence that somebody is operating unlicensed.
Can it detect revocations in every covered state?
In six of the eight. Texas (TDLR) publishes an expiration date but no status field, so expiry monitoring works there and a revocation would not be seen. South Carolina (LLR) is the mirror image: it publishes a status but never an expiration date, so status monitoring works there and expiry does not. The other six support both.
Do I need my own Notion or Slack app?
Notion, no - Apify manages that OAuth, so you authorize it in one click from the input form. Slack, yes - Slack requires you to register your own Slack OAuth app first. That is Slack's requirement rather than ours. You can also leave the connector empty and just use the dataset.
How am I charged?
Per licence this monitor actually resolves on a state board. Rows that come back not-found, uncovered-state or check-failed are free. Separately, the per-state lookup Actors this monitor calls on your behalf charge their own per-match fees - the README has a measured worked example with both lines.
Why are New Mexico and South Carolina slower and more expensive?
Both boards put a CAPTCHA on the search itself, so each licence costs a solve. In a measured run on 2026-08-06, 12 South Carolina licences took 244 seconds against 10 seconds for 13 Washington licences, and the two CAPTCHA states accounted for 73% of the underlying lookup cost of a 93-contractor book. Keep that in mind when sizing a watchlist.
How does it avoid matching the wrong contractor?
A watchlist row with a license number is matched on that number exactly. A name-only row is matched on the full normalized business name (case, punctuation and LLC/Inc suffixes ignored) - a partial or near match is not accepted. If a name resolves to more than one licence the row is returned as 'check failed - manual review' rather than the Actor guessing, because billing you for a verdict about the wrong company is worse than returning nothing.
Is this personal data?
It is public professional-licence data as the state boards publish it. Some boards licence individuals rather than companies, so a licensed person's name can appear where the board publishes it that way. No personal or PII enrichment is added.
Guides & use cases
Related actors