GTM Playbooks
OutboundIntermediate

Weekly New Contractor Licenses into Clay for Outbound

The problem

Newly licensed contractors are setting up insurance, software, suppliers, financing, and payroll for the first time, but each state board publishes licenses in its own format, so reps find them late or never.

Partner data source

Multi-State Contractor License Search on Apify

An Apify Actor that reads contractor licenses from official state open data and bulk files, returns one schema for every state, and has a monitor mode that returns only licenses it has not seen before.

Open the Actor on Apify
  • States: California, Texas, Washington, Oregon, and Virginia
  • Weekly feed: mode newLicenses with a monitorId returns only licenses not seen in earlier runs
  • Filters: tradeKeywords, status, and issuedAfter, plus maxResults caps
  • Output: state, licenseNumber, businessName, licenseType, status, issueDate, expirationDate, address, and phone or email where the state publishes it

Partner link: GTM Playbooks has a commercial relationship with this data source. See the affiliate disclosure.

Implementation steps

  1. 1

    Schedule the Multi-State Contractor License Search Actor on Apify weekly: save a task with mode set to newLicenses, a monitorId for this search (for example ca-wa-roofing-weekly), the states you sell into (CA, TX, WA, OR, VA), tradeKeywords for your buyers (roofing, electrical, hvac, or state codes such as C39), status set to active, and maxResults as a spend cap, then add an Apify schedule that runs the task every Monday morning

  2. 2

    Send each run into a Clay table: add Clay's Apify source (Import data from Apify Actor) pointed at the saved task with no specific run selected so later runs flow in, or use a Zapier or Make scenario on the Apify finished-run trigger that fetches the run's dataset items and posts each record to a Clay webhook table

  3. 3

    Dedupe on state plus licenseNumber: build a license_key such as OR-261748 (in the Zapier or Make step, or as a Clay formula column) and turn on Clay's Auto-dedupe rows on license_key with Keep oldest row, so a rerun or an overlapping monitor never enriches the same license twice

  4. 4

    Skip records already in the CRM: look up the company by a contractor_license_key property first, then by phone, then by business name plus city, and set every later column to run only when no match comes back; matches go to an "Already in CRM" view for the account owner instead of a sequence

  5. 5

    Enrich the owner or decision maker: start from licenseeName (the owner, qualifier, or principal the state lists), have Claygent find the business website from businessName, city, and state, find people titled Owner, President, or Founder when a domain exists, then run an email waterfall and validate the result; keep the state-published phone (CA, TX, WA, OR) or email (VA) as a fallback

  6. 6

    Route by territory: look up address.state and address.county (or ZIP) in a territory sheet to get the owner, then create the company and contact in HubSpot or Salesforce with license fields, lead source "Contractor license feed", and that owner

  7. 7

    Send to a sequence: add contacts with a validated email to an Instantly or Smartlead campaign for their trade, with business name, license type, issue date, and city as custom variables, and create a call task for records that only have a published phone; follow CAN-SPAM for email and check TCPA rules before calling or texting

What it costs to run

The Actor charges per event at the prices listed on its Apify page. Clay credits are separate.

Listed prices
ChargeListed price
New-license lead (newLicenses mode)$0.01 each
License record (lookup mode)$0.003 each
Run start, listed on the Actor page$0.005 per run

Worked examples

500 new licenses in a weekly run
500 x $0.01 = $5.00 in Actor cost, plus $0.005 for the run
2,000 records in a one-time lookup to backfill a territory
2,000 x $0.003 = $6.00 in Actor cost, plus $0.005 for the run
Clay credits for website, people, email, and validation steps
Vary by provider and by how many rows reach each step
  • Cap Actor spend with maxResults and maxResultsPerState on the saved task, and a maximum charge per run.
  • Before enriching a full week, run the Clay columns on 10 rows and check the credit estimate.
  • Prices change; confirm them on the Actor page before scheduling.

Template outline

The structure to recreate in your own workspace. Field names and thresholds are starting points.

Apify saved task (Multi-State Contractor License Search):
{
  "mode": "newLicenses",
  "monitorId": "ca-wa-or-roofing-weekly",
  "states": ["CA", "WA", "OR"],
  "tradeKeywords": ["roofing", "C39"],
  "status": "active",
  "maxResults": 500,
  "maxResultsPerState": 250
}
- Schedule: Apify schedule, cron 0 13 * * 1 (Mondays 13:00 UTC), runs the saved task
- One monitorId per saved search; a new monitorId starts a new baseline
- First run returns licenses from the last 7 days (or since issuedAfter)
- Texas publishes no issue dates, so the first run returns no Texas records

Clay table: Contractor licenses (weekly)
Source, pick one:
  A. Apify source "Import data from Apify Actor": saved task, no specific run
  B. Webhook table fed by Zapier or Make: Apify "Finished Actor Run" trigger
     > "Fetch Dataset Items" > one POST per record, with license_key added
Input columns: state, licenseNumber, businessName, licenseeName, dbaName,
  licenseType, licenseTypeCode, status, issueDate, expirationDate,
  address.street, address.city, address.zip, address.county, phone, email

1. license_key = state + "-" + licenseNumber (OR-261748)
   Table settings: Auto-dedupe rows on license_key, Keep oldest row
2. CRM lookup (HubSpot "Look up object" / Salesforce "Lookup record"):
   contractor_license_key = license_key, then phone, then business name + city
   - Match found: in_crm = true, row goes to "Already in CRM" view, stop
3. Website (only if in_crm is false): Claygent
   "Find the website for {{businessName}}, a {{licenseType}} in {{address.city}}, {{state}}.
    Return the domain or none."
4. Decision maker:
   - Name: licenseeName (owner, qualifier, or principal on the license)
   - If a domain exists: find people at domain, titles Owner, President, Founder
5. Email: waterfall across your providers, then validation
   - Fallback: state-published email (VA only), validated before use
   - Phone: state-published phone (CA, TX, WA, OR)
6. Territory: lookup in "Territories" sheet on state + county (or ZIP)
   -> owner_email, territory_name
7. CRM write: create company + contact, owner = owner_email

CRM properties to create:
- Company: contractor_license_key (unique), license_state, license_number,
  license_type, license_issue_date, license_expiration_date,
  lead_source = "Contractor license feed"
- Contact: license_role (owner, qualifier, principal), phone_source = "State license record"

Sequence (one campaign per trade):
- Variables: {{first_name}}, {{business_name}}, {{license_type}}, {{issue_date}}, {{city}}
- Opener example (insurance): "Saw {{business_name}} was issued a {{license_type}}
  license in {{city}} on {{issue_date}}. If you are setting up coverage for the new
  license, here is a one-page checklist of what carriers ask for."
- Swap the last line per offer: software, supplies, financing, payroll

Guardrails:
- Only statusCategory = active
- No sequence without a validated email; phone-only records get a call task
- Email: CAN-SPAM opt-out and postal address; calls and texts: check TCPA first
- Say you saw the license in the state record; never imply a link to the state board

Need help implementing a playbook?

Our team builds custom Clay workflows and AI automations for revenue teams.

Get it built for you