Target Account Scanner
π‘ GrowthScans firmographic, technographic, hiring, funding, and intent signals to build the ICP-matched target account universe
Adam's take
If you only build one agent this year, build this one. Every downstream agent inherits the quality of the universe it creates, and most teams discover their 'ICP' was three opinions in a trench coat the moment they have to write the config.
Required tools
Optional tools
Expected value
Replaces 10-20 hrs/month of manual account research; consistent ICP coverage
Deployable workflow pack
The full pack includes the agent spec, deployment guide, architecture diagrams, n8n workflow skeleton, Clay template, HubSpot properties, JSON schemas, OpenAPI spec, cost and ROI calculators, demo data, QA checklist, and governance notes.
View pack on GitHubHow to deploy this agent
Most teams define an ICP once, in a slide, and never operationalize it. This agent turns the ICP definition into a versioned, running scan against firmographic, technographic, and intent data, and writes the result back to HubSpot as agent-owned properties.
Core signals
Input schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deploytheagent.com/schemas/agent-001/input.schema.json",
"title": "Target Account Scanner β Input",
"description": "One candidate account record entering the scoring step. Enrichment fields may be null (scored neutral or zero per workflow logic); required identity and CRM-gate fields may not.",
"type": "object",
"required": ["domain", "company_name", "crm_status"],
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$",
"description": "Normalized root domain, lowercase, no protocol or www"
},
"company_name": { "type": "string", "minLength": 1 },
"industry": { "type": ["string", "null"] },
"employee_count": { "type": ["integer", "null"], "minimum": 0 },
"revenue_usd": { "type": ["integer", "null"], "minimum": 0 },
"hq_country": { "type": ["string", "null"], "description": "ISO 3166-1 alpha-2" },
"hq_region": { "type": ["string", "null"] },
"tech_stack": {
"type": "array",
"items": { "type": "string" },
"default": []
},
"hiring": {
"type": "object",
"additionalProperties": false,
"properties": {
"open_roles_target_function": { "type": ["integer", "null"], "minimum": 0 },
"functions": { "type": "array", "items": { "type": "string" } }
}
},
"funding": {
"type": "object",
"additionalProperties": false,
"properties": {
"last_round": { "type": ["string", "null"] },
"last_round_date": { "type": ["string", "null"], "format": "date" },
"last_round_amount_usd": { "type": ["integer", "null"], "minimum": 0 }
}
},
"intent": {
"type": "object",
"additionalProperties": false,
"properties": {
"level": { "type": ["string", "null"], "enum": ["surging", "moderate", "none", null] },
"topics": { "type": "array", "items": { "type": "string" } },
"provider": { "type": ["string", "null"] }
}
},
"crm_status": {
"type": "object",
"required": ["lifecycle_stage", "has_open_deal"],
"additionalProperties": false,
"properties": {
"lifecycle_stage": { "type": ["string", "null"] },
"has_open_deal": { "type": "boolean" },
"is_customer": { "type": "boolean", "default": false }
}
},
"source": {
"type": "string",
"description": "Where the candidate came from: apollo, import, web_visitor, manual"
}
}
}Output schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://deploytheagent.com/schemas/agent-001/output.schema.json",
"title": "Target Account Scanner β Output",
"description": "One scored account record leaving the workflow. Written to HubSpot agent-owned properties and included in the Slack digest.",
"type": "object",
"required": [
"domain",
"icp_match_score",
"icp_match_band",
"icp_match_reasons",
"icp_scan_date",
"icp_model_version",
"gate_result",
"score_breakdown"
],
"additionalProperties": false,
"properties": {
"domain": { "type": "string" },
"icp_match_score": { "type": "integer", "minimum": 0, "maximum": 100 },
"icp_match_band": { "type": "string", "enum": ["Target", "Monitor", "Exclude"] },
"icp_match_reasons": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"description": "One line per scoring dimension, with source attribution"
},
"icp_scan_date": { "type": "string", "format": "date" },
"icp_model_version": {
"type": "string",
"description": "Config version plus prompt version when LLM reasons are enabled, e.g. icp-config-1.0.0/reasons-off"
},
"gate_result": {
"type": "string",
"enum": ["written", "skipped_customer", "skipped_open_deal", "skipped_excluded_domain"],
"description": "Whether the record was written to CRM or suppressed, and why"
},
"score_breakdown": {
"type": "object",
"required": ["firmographic", "technographic", "signals"],
"additionalProperties": false,
"properties": {
"firmographic": { "type": "integer", "minimum": 0, "maximum": 55 },
"technographic": { "type": "integer", "minimum": 0, "maximum": 15 },
"signals": { "type": "integer", "minimum": 0, "maximum": 30 }
}
},
"confidence": {
"type": ["number", "null"],
"minimum": 0,
"maximum": 1,
"description": "Share of scoring fields that were non-null; low confidence flags enrichment gaps, not bad fit"
}
}
}Scoring / classification logic
Accounts scoring 70+ land in the Target band and get written to HubSpot. 50-69 is Monitor. Below 50 is Exclude. Every scoring reason is logged per account with source attribution, and the config version is stored alongside the score so a team can trace exactly which weights produced a given result.
HubSpot CRM properties
n8n workflow skeleton
Weekly ICP scan: pull candidates from Clay, score against ICP config, gate on CRM status, write agent-owned HubSpot properties, post Slack digest. Configure every node marked CONFIGURE in its notes.
- Weekly scan triggerCONFIGURE: default Monday 06:00. First universe build: run manually.
- Load ICP configCONFIGURE: paste your versioned icp-config JSON here or fetch from repo raw URL. Version string flows to icp_model_version.
- Fetch candidates from ClayCONFIGURE: Clay table webhook/HTTP API URL and {{CLAY_API_KEY}}. Expects rows matching clay-template.csv headers.
- Validate against input schemaDrops rows failing schemas/input.schema.json shape; logs drops. Do not silence the log.
- Score ICP matchImplements the scoring table from agent-spec.md. Weights come from config, not code edits.
- CRM status gateSuppression first: customers and open-deal accounts are never written. Gate failures block the run rather than passing records through.
- Write agent-owned propertiesCONFIGURE: {{HUBSPOT_CREDENTIAL}}. Writes ONLY the five icp_* properties from hubspot/properties.json. Batch 100 per call. Only gate_result=written records reach this node (add an IF node filter on import if your n8n version needs it explicit).
- Post Target-band digestCONFIGURE: {{SLACK_CREDENTIAL}} and channel. Digest lists new Target-band accounts with score, top reasons, and a review link. Humans approve before any outbound use.
- Write run logAudit trail: run date, config version, counts per band, per-field fill rates, gate skips. Alert if Target band >25% of universe or no run in 8 days (pair with a separate monitor workflow).
Agent-driven Clay integration (optional)
The default deployment path pulls Clay data through a static export URL, shown above. Teams already running Clay through its official CLI/MCP (clay-run/agent-plugins) can use this agent-driven path instead.
Clay MCP/CLI Integration β Agent #001
Pack Version: 1.0.0
Optional path. Use this instead of a manual `clay-template.csv` import when the n8n workflow's `Fetch candidates from Clay` node should pull from the `to_scanner` view through the official Clay CLI/MCP rather than a static export URL.
Source: `clay-run/agent-plugins` (github.com/clay-run/agent-plugins), the official Clay plugin for Claude Code, Cursor, and Codex. Bundles a `clay` CLI and an MCP server, authenticated with a `CLAY_API_KEY`.
When to use this
The scanner's n8n workflow calls Clay on every scheduled run to read the `to_scanner` view. If that call runs through an agent runtime (n8n's HTTP node hitting the Clay API directly already works and stays the default), the MCP/CLI path is worth it only when the same runtime also needs to trigger Clay-side actions beyond a read: re-running enrichment on a subset, adding rows from a new candidate source, or checking `clay whoami` as a pre-flight credential check before a scheduled run.
If the workflow only reads the `to_scanner` view on a schedule, the existing HTTP export pulls that off with no added dependency. Don't add this file's setup to a deployment that doesn't need it.
Setup
- Create a Clay API key: Clay β Settings β Account.
- Store it as `CLAY_API_KEY` in the n8n credential store, scoped to this workflow. Never place it in the workflow JSON or the repo.
- Install the plugin in the build environment: `/plugin marketplace add clay-run/agent-plugins` then `/plugin install clay@clay-plugins` (Claude Code), or the Codex/Cursor equivalents in the plugin README.
- Verify before first production run: `clay whoami`. Exit 0 confirms the key resolves to the correct workspace. Exit 3 means the key is missing or invalid.
Scope for this agent
- Read `to_scanner` view: unattended, matches the existing governance flags for this agent.
- Trigger re-enrichment on `pre_filter = pass` rows: unattended, same cost profile as the scheduled enrichment refresh already documented in `clay-setup-guide.md`.
- Add new candidate rows from a source the agent discovers on its own: not allowed. Candidate sourcing stays a deliberate step a human configures, consistent with `no_auto_crm_writes_without_review` in this agent's Governance section.
Governance addition
Log every Clay CLI/MCP call this workflow makes (view, action, row count, timestamp) alongside the existing `audit_trail_required` flag. Rotate `CLAY_API_KEY` if the n8n instance or its execution logs are ever exposed.
Fallback
The default deployment path stays the CSV export view described in `clay-setup-guide.md`. This file documents the agent-driven alternative for teams already running Clay through its official CLI/MCP elsewhere in their stack.
Cost calculator
Cost Calculator β Agent #001
Pack Version: 1.0.0
Cost drivers
| Driver | Unit | Typical price | Notes |
|---|---|---|---|
| Firmographic enrichment | credit/row | $0.05β$0.15 | Clearbit/Apollo via Clay credits |
| Tech stack detection | credit/row | $0.05β$0.10 | BuiltWith/Wappalyzer |
| Hiring scrape | credit/row | $0.02β$0.05 | Clay native |
| Funding lookup | credit/row | $0.05β$0.10 | Crunchbase/Harmonic |
| Intent | contract | $15Kβ$60K/yr | Only if 6sense/Demandbase already owned; don't buy it for this agent |
| n8n | instance | $0β$50/mo | Self-hosted or starter cloud |
| LLM reasons (optional) | tokens/row | ~$0.001β$0.005 | Off by default; rule-generated reasons are free |
Assumptions: Clay credits at blended ~$0.07/credit on a Pro plan; 4 paid enrichment column groups per row on first run; 30-day cache on firmographics, weekly rerun on hiring only (~25% of rows re-enriched per month after month one).
Worked scenarios (monthly, steady state after initial build)
| 100 records/mo | 1,000 records/mo | 10,000 records/mo | |
|---|---|---|---|
| New-row enrichment (4 credits Γ $0.07) | $28 | $280 | $2,800 |
| Weekly hiring refresh on existing universe | $6 | $56 | $560 |
| n8n | $0β$50 | $0β$50 | $50 |
| Total (ex-intent, ex-seats) | ~$34β$84 | ~$336β$386 | ~$3,410 |
First-run cost for the initial universe equals the new-row line at your full universe size (e.g. 5,000 candidates β $1,400). The pre-enrichment filter in the Clay guide is the lever: filtering 40% of raw candidates before enrichment cuts these numbers by 40%.
What this replaces
10β20 operator hours/month at a loaded $75/hr = $750β$1,500/month. The crossover argument lives in `roi-calculator.md`.
ROI calculator
ROI Calculator β Agent #001
Pack Version: 1.0.0
Fill the blanks with your numbers. Defaults are conservative; the model claims time savings and list quality, not revenue you can't attribute.
Time savings
| Input | Default | Yours |
|---|---|---|
| Hours/month building and refreshing lists today | 12 | ___ |
| Loaded hourly cost of that operator | $75 | ___ |
| Hours/month reviewing scanner output instead | 2 | ___ |
| Monthly time saving | (12 β 2) Γ $75 = $750 | ___ |
Running cost
| Input | Default | Yours |
|---|---|---|
| Monthly enrichment + tooling (from cost-calculator.md) | $386 (1,000 rec/mo) | ___ |
| Build cost amortized over 12 months (1 day Γ $600 / 12) | $50 | ___ |
| Monthly cost | $436 | ___ |
Net and break-even
Default: $750 β $436 = $314/month net, break-even in month 1 including build cost. At 100 records/month the default nets ~$616/month; at 10,000 records the time-saving argument alone doesn't carry it and the case must rest on list quality (below) β say so honestly in your business case.
List quality (model only if you can measure it)
Poor-fit accounts entering sequences waste SDR time and depress reply rates. If you can measure your current poor-fit rate:
| Input | Yours |
|---|---|
| Accounts worked/month | ___ |
| % outside ICP today | ___ |
| SDR hours per worked account | ___ |
| Hours redirected to Target-band accounts | = accounts Γ poor-fit % Γ hrs |
No default here. If you can't measure your poor-fit rate yet, leave this section out of the business case rather than inventing a benchmark.
Governance notes
- Suppression runs before scoring reaches CRM: accounts already tagged customer or with an open deal are never written, regardless of score (gate_result: skipped_customer / skipped_open_deal).
- Writes land only in agent-owned HubSpot properties β never an existing lead-scoring or lifecycle-stage field.
- Every scored record carries a model/config version and a scan date for auditability.
- The workflow does not create CRM contacts. Contact-level sourcing is a separate, reviewed process (Agent #005).
Failure modes
- Missing firmographic data: nulls are scored neutral where the config defines a neutral default (e.g. unknown revenue), not zero β this avoids penalizing gaps in enrichment as if they were poor fit.
- Stale or missing intent: no intent data scores as "none," not as a false positive.
- Duplicate accounts: rows are deduplicated by domain before scoring.
- Runaway Target band: if more than roughly a quarter of the scanned universe lands in Target, that signals the config is too loose and should be retuned before the list is trusted for outbound.
QA checklist
QA Checklist β Agent #001
Pack Version: 1.0.0
Complete on a test HubSpot portal or sandboxed property group before production. The reviewer must not be the builder.
Data validation
- β All four demo-data records pass `schemas/input.schema.json`
- β Workflow output on demo data matches `demo-data/sample-output.json` field for field (scores, bands, breakdowns, gate results)
- β Output validates against `schemas/output.schema.json`
- β A row with null revenue scores neutral (5), not zero
- β A row missing a required field is dropped and logged, and does not fail the run
Scoring
- β Weights in the running config match the agreed, versioned ICP config
- β Acme Analytics scores 88 / Target; Petal & Stem scores 10 / Exclude
- β Band thresholds read from config: changing target threshold to 80 demotes Acme without a code edit
- β Disqualifying tech sets the flag and zeroes the 5-point component
Gates and CRM safety
- β Northwind Logistics (customer) returns `skipped_customer` and produces no HubSpot write
- β An account with an open deal returns `skipped_open_deal` and produces no write
- β Writes land only in the five `icp_*` properties; diff a test company's full property set before/after to confirm
- β Private app scopes limited to companies read/write + deals read
- β `icp_model_version` populated on every written record
Operations
- β Slack digest posts to the agreed channel with score and reasons per account
- β Run log records band distribution and gate skips
- β Target band β€25% of test universe (if higher, weights are too generous; stop and retune)
- β Duplicate domain in source data scores once, not twice
- β Rollback tested: workflow disabled, test property values cleared
Sign-off
| Role | Name | Date |
|---|---|---|
| Builder | ||
| Reviewer (not the builder) | ||
| RevOps owner (ICP config) |