HubSpot Enrichment from the Claude Code CLI with Scavio
Replace Apollo and Clay for HubSpot enrichment with an 80-line Claude Code script. Fresh public data per contact, under $3/mo for 1,000 enrichments.
Every founder-led sales team hits the same wall: HubSpot has your contacts, but your contacts are under-enriched. Tools like Clay and Apollo fix that for a price. The 2026 pattern replaces both with a Claude Code CLI workflow that pulls contacts, enriches from public data via Scavio, and writes back to HubSpot properties.
The whole pipeline is ~80 lines of TypeScript, runs on your laptop, and costs under $30/mo total.
The Architecture
Claude Code is the orchestrator. HubSpot is the store. Scavio is the public-data source. No third party in the middle.
- Claude pulls contacts missing key fields from HubSpot
- For each, calls Scavio SERP + Reddit to build a signal bundle
- LLM synthesizes a one-line brief and a mention count
- Claude writes the brief and mention count back to custom HubSpot properties
The HubSpot Setup
Create two custom properties on the contact object before running:
scavio_brief: single-line text, up to 500 charsscavio_signal_count: number, 0-1000
Mint a HubSpot private app token with crm.objects.contacts.readand crm.objects.contacts.write scopes.
The Enrichment Script
import { Scavio } from 'scavio';
const scavio = new Scavio({ apiKey: process.env.SCAVIO_API_KEY });
const HUBSPOT = process.env.HUBSPOT_TOKEN;
async function getContacts() {
const r = await fetch(
'https://api.hubapi.com/crm/v3/objects/contacts?limit=100&properties=email,company_domain,scavio_brief',
{ headers: { Authorization: `Bearer ${HUBSPOT}` } }
);
return (await r.json()).results.filter((c: any) => !c.properties.scavio_brief);
}
async function enrich(contact: any) {
const domain = contact.properties.company_domain;
if (!domain) return null;
const [serp, reddit] = await Promise.all([
scavio.search({ query: `${domain} funding news` }),
scavio.search({ platform: 'reddit', query: domain })
]);
return {
brief: serp.organic_results?.[0]?.snippet || 'No recent news',
signal_count: (reddit.organic_results?.length || 0) + (serp.organic_results?.length || 0)
};
}
async function writeBack(id: string, brief: string, count: number) {
await fetch(`https://api.hubapi.com/crm/v3/objects/contacts/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${HUBSPOT}`,
'content-type': 'application/json'
},
body: JSON.stringify({
properties: { scavio_brief: brief, scavio_signal_count: count }
})
});
}
const contacts = await getContacts();
for (const c of contacts) {
const signal = await enrich(c);
if (signal) await writeBack(c.id, signal.brief, signal.signal_count);
}Running It From Claude Code
Drop the script into ~/.claude/skills/hubspot-enrich/index.tsand register it in the skill manifest. Every Claude Code session now has a command like:
> Enrich the 50 newest HubSpot contacts with Scavio signals.Claude handles the batch, reports which contacts got enriched, and writes the brief back without leaving the terminal.
Cost Per Contact
Two Scavio calls per contact, ~60 credits total (~$0.003). A team enriching 1,000 contacts a month spends ~$3 on Scavio, compared to $149/mo Clay Starter plus per-record top-ups. The HubSpot token costs nothing.
The Scheduling Wrinkle
Most teams run this as a nightly cron on a small EC2 or a Mac Mini. Claude Code can kick it off via a launchd agent on macOS or a simple systemd timer on Linux. Nothing fancy; the script is idempotent because it only pulls contacts with empty briefs.
The full reference pipeline is in the HubSpot + Claude Code + Scavio workflow, including the launchd and systemd configs.