PluginBench
Skill
Pass
Audit score 90

audience-targeting

hubspot/agent-cli-skills

Build targeted contact segments by filtering on lifecycle, engagement, job title, geography, and firmographics—then export as JSONL.

What is audience-targeting?

This skill lets you construct precise contact audiences by applying filters across lifecycle stage, email engagement, job title, geography, and company attributes. Use it to identify prospects, decision-makers, or engaged leads for campaigns or downstream tools.

  • Filter contacts by lifecycle stage, lead status, email engagement, job title, and geography
  • Target decision-makers and specific roles using token-based job title matching
  • Build cross-object segments by finding companies in an industry, then fetching their associated contacts
  • Export filtered audiences as JSONL for campaigns, bulk operations, or external tools
  • Save and reuse segments for updates, re-fetches, or membership management
  • Count audience size before export without full pagination

How to install audience-targeting

npx skills add https://github.com/hubspot/agent-cli-skills --skill audience-targeting
Prerequisites
  • HubSpot CLI installed and authenticated
  • Familiarity with `hubspot objects search` command syntax
  • Understanding of your portal's contact properties and enum values
Claude Code
Cursor
Windsurf
Cline

How to use audience-targeting

  1. 1.Run `hubspot objects search --type contacts` with `--filter` flags matching your criteria (lifecycle, engagement, job title, geography)
  2. 2.Use `@=` operator for enum lists to combine multiple values efficiently (e.g., `lifecyclestage@=lead,customer`)
  3. 3.For cross-object filtering (e.g., companies by industry), search companies first, then use `associations list` and `objects get` to fetch their contacts
  4. 4.Pipe results to `jq` to reshape, drop opted-out contacts, or extract specific properties
  5. 5.Save the JSONL output to a file for reuse, or pipe directly to `hubspot objects update` for bulk operations
  6. 6.Use `hubspot objects count` to size your audience before exporting

Use cases

Good for
  • Identify recent unowned leads from this quarter for immediate sales outreach
  • Find all VPs, directors, and C-suite contacts across your database for executive targeting
  • Build an engaged-but-not-yet-qualified segment (opened email recently, still in lead stage, opted in)
  • Target all contacts at software companies with 100+ employees in a specific geography
  • Create a segment of contacts with active pipeline deals for account-based marketing
Who it's for
  • Sales development representatives building prospect lists
  • Marketing teams segmenting audiences for campaigns
  • Sales leaders identifying high-value decision-makers
  • RevOps professionals managing bulk contact operations
  • Account-based marketing teams targeting specific company verticals

audience-targeting FAQ

How do I target multiple job titles or lifecycle stages at once?

Use the `@=` operator with comma-separated values in a single `--filter` flag: `jobtitle@=director,vp,chief` or `lifecyclestage@=lead,customer`. This counts as one condition and is more efficient than multiple `--filter` flags.

Can I filter on company attributes like industry or employee count?

Yes, but those properties live on the company object, not contacts. Search companies first with `--filter "industry=SOFTWARE AND numberofemployees>=100"`, then use `associations list` to find their associated contacts, and batch-fetch with `objects get`.

What does the `~` operator do, and why doesn't it work like substring search?

The `~` operator matches whole tokens in a field (e.g., `jobtitle~director` matches "Director of Sales" but not "directorship"). It is not regex or substring matching. For broader matches, search broadly and post-filter results with `jq`.

How do I exclude opted-out contacts from my segment?

Add `hs_email_optout!=true` to your filter, or pipe the JSONL output through `jq -c 'select(.properties.hs_email_optout != "true")'` to drop them after export.

Can I save a segment in HubSpot for reuse, or must I export JSONL?

You can do both. Save the JSONL file locally for portability and bulk operations, or use `hubspot segments create` to persist a dynamic list in HubSpot, or `hubspot views create` to save a filter set as a reusable view.

Full instructions (SKILL.md)

Source of truth, from hubspot/agent-cli-skills.


name: audience-targeting description: Build a targeted contact segment by filtering on lifecycle, engagement, jobtitle, geography, or firmographics — then export it as JSONL for a campaign or downstream tool. triggers:

  • "segment contacts"
  • "target audience"
  • "find prospects"
  • "build audience"
  • "contact segmentation"
  • "contacts in industry"
  • "decision makers"
  • "engaged contacts"

Foundation

Read bulk-operations/SKILL.md first — pagination, JSONL piping, destructive-op safety. Reshape recipes in bulk-operations/resources/json-patterns.md. Resource: resources/contact-segmentation-filters.md is the filter-expression cookbook (lifecycle, lead status, email engagement, activity, deals, owner).

Filter syntax cheat sheet

Source of truth: hubspot objects search --help.

  • One --filter flag = one AND group: --filter "lifecyclestage=lead AND !hubspot_owner_id".
  • Multiple --filter flags are OR'd. Use for enum-OR-enum.
  • Operators: =, !=, >, >=, <, <=, ~ (CONTAINS_TOKEN — whole-word, NOT substring), @= (IN), @!= (NOT_IN).
  • HAS_PROPERTY: bare name or name?. NOT_HAS_PROPERTY: !name. Dates: YYYY-MM-DD.
  • Limits (CRM search API): max 5 --filter flags, max 6 conditions per flag, max 18 conditions total. Prefer @= / @!= for enum lists — lifecyclestage@=lead,customer is one condition, --filter "lifecyclestage=lead" --filter "lifecyclestage=customer" is two groups.
  • "None of these, or empty" needs two groups — @!= skips records where the property is unset, so OR in a !name group and repeat the shared conditions in both: --filter "hubspot_owner_id=123 AND lifecyclestage@!=customer,evangelist" --filter "hubspot_owner_id=123 AND !lifecyclestage"

~ gotcha: jobtitle~director matches the token "director", not arbitrary substrings. No regex operator — search broadly, post-filter with jq.

Properties this skill turns on

Full live list: hubspot properties list --type contacts. Enum options aren't exposed by properties get; discover with hubspot objects list --type contacts --properties <name> --limit 100 --format json | jq -r '.data[].properties.<name> // empty' | sort -u.

Core fields used here: lifecyclestage, hubspot_owner_id (bare/! for owned/unowned; hubspot owners list for IDs), hs_email_optout (!=true excludes opted-out), hs_email_last_open_date / notes_last_contacted (recency), jobtitle / country / city (string = or ~), num_associated_deals (0 net-new, >=1 has-pipeline).

Firmographics (industry, numberofemployees, annualrevenue) live on companies — see cross-object section.

Common segments

# Recent leads (this quarter, not yet owned)
hubspot objects search --type contacts \
  --filter "lifecyclestage=lead AND createdate>2026-01-01 AND !hubspot_owner_id" \
  --properties email,firstname,lastname,createdate

# Decision-makers by jobtitle (OR across tokens)
hubspot objects search --type contacts \
  --filter "jobtitle~director" --filter "jobtitle~vp" --filter "jobtitle~chief" \
  --properties email,jobtitle,company

# Engaged but not yet MQL (opened recently, still lead, opted in)
hubspot objects search --type contacts \
  --filter "lifecyclestage=lead AND hs_email_last_open_date>2026-04-01 AND hs_email_optout!=true" \
  --properties email,firstname,hs_email_last_open_date

# Geographic — US contacts opted in
hubspot objects search --type contacts \
  --filter "country=United States AND hs_email_optout!=true" \
  --properties email,state,city

More patterns (lead status, deals, owners, combined AND/OR) in resources/contact-segmentation-filters.md.

Cross-object: companies-in-industry → their contacts

industry/numberofemployees/annualrevenue live on the company. Build the company set, then traverse — never xargs -I{} hubspot objects get per company. associations list emits {"id":"...","type":"company_to_contact"}, feeding directly into a single batched objects get.

# Step 1: target companies. Industry options are portal-specific — discover with:
#   hubspot objects list --type companies --properties industry --limit 100 --format json \
#   | jq -r '.data[].properties.industry // empty' | sort -u
hubspot objects search --type companies \
  --filter "industry=SOFTWARE AND numberofemployees>=100" \
  --properties name,industry,numberofemployees \
  > target_companies.jsonl

# Step 2: gather association IDs (associations list has no batch --from), then ONE batched
# objects get for all contacts.
while read -r cid; do hubspot associations list --from "companies:$cid" --to contacts; done \
  < <(jq -r '.id' target_companies.jsonl) \
| jq -c '{id}' | sort -u \
| hubspot objects get --type contacts --properties email,firstname,jobtitle,hs_email_optout \
> target_contacts.jsonl

# Optional: drop opted-out
jq -c 'select(.properties.hs_email_optout != "true")' target_contacts.jsonl > campaign_audience.jsonl

Saving and reusing a segment

A segment is a JSONL file. Re-use for updates, exports, or re-fetches:

# Save
hubspot objects search --type contacts \
  --filter "lifecyclestage=lead AND hs_email_optout!=true" \
  --properties email,firstname,lastname,jobtitle \
  > segments/opted_in_leads.jsonl

# Assign owner (dry-run first per bulk-operations/SKILL.md)
jq -c '{id, properties:{hubspot_owner_id:"12345"}}' segments/opted_in_leads.jsonl \
| hubspot objects update --type contacts --dry-run

# Re-fetch with different properties later
jq -c '{id}' segments/opted_in_leads.jsonl \
| hubspot objects get --type contacts --properties email,lifecyclestage,hs_lead_status

Destructive ops on a saved segment follow the dry-run → digest → confirm flow in bulk-operations/SKILL.md.

Save as a HubSpot list or view

A JSONL segment is portable, but you can also persist an audience in HubSpot:

  • List — hubspot segments create saves a list; segments get / list inspect it; segments members-list reads members; segments members-add / members-remove manage membership. Use segments update-filters to set a dynamic list's filter criteria.
  • View — hubspot views create --type <t> --name "..." --columns a,b [--filters-file filters.json] [--sort prop:asc] saves a filter set as a reusable object view; views list / get / update / replace-field / delete manage it.
  • Size it first — hubspot objects count --type contacts --filter "..." returns {"object_type":"contacts","total":N} without paging, so you can size an audience before saving or exporting it.

Known limits

  • ~ is token-match, not substring. No regex operator.
  • properties get does not return enum options — discover via objects list + jq.
  • associations list has no batch --from. Loop to gather IDs, batch the downstream objects get.
  • For >100 results, use the pagination loop in bulk-operations/SKILL.md.