hubspotclaude-codeenrichment

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.

5 min read

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.

  1. Claude pulls contacts missing key fields from HubSpot
  2. For each, calls Scavio SERP + Reddit to build a signal bundle
  3. LLM synthesizes a one-line brief and a mention count
  4. 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 chars
  • scavio_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:

Bash
> 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.