Developer Reference

VibeJoy API & Developer Docs

Control your entire outbound pipeline from your AI agent, CLI, or any REST client. Find leads by intent signal, enrich contacts, generate campaigns, and fire email sequences without touching the dashboard.

MCP: Claude & Cursor
AI Agents (any LLM)
CLI
REST API
n8n + Make.com

Quickstart

Get your first leads in under 5 minutes. Pick your preferred interface:

Connect VibeJoy to Claude Desktop or Cursor. Your agent can then find leads, enrich contacts, and run full campaigns through natural-language prompts.

1

Get your API key

Go to Settings → Developer and click Generate API Key. It starts with vj_.

2

Add to your global Cursor MCP config

~/.cursor/mcp.json
{
  "mcpServers": {
    "vibejoy": {
      "command": "node",
      "args": ["/path/to/vibejoy/mcp/index.js"],
      "env": {
        "VIBEJOY_API_KEY": "vj_your_api_key_here",
        "VIBEJOY_API_URL": "http://localhost:3001"
      }
    }
  }
}

For Claude Desktop on macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

For Claude Desktop on Windows: %APPDATA%\Claude\claude_desktop_config.json

3

Reload Cursor (or restart Claude Desktop)

In Cursor: Ctrl+Shift+P → Reload Window. The vibejoy tools will appear in your agent's tool list.

4

Prompt your agent

Try any of these to confirm it's working:

"What are my VibeJoy dashboard stats?"

get_dashboard_stats

"What's my brand context?"

get_brand_context

"Find 10 companies hiring SDRs in London"

find_leads

Run a Full Campaign

Three things required before a campaign sends — all fully automatable

1. Brand context — set once via MCP (setup_brand), CLI (vibejoy brand setup), or dashboard.

2. SendGrid — connect once via MCP (connect_sendgrid), CLI (vibejoy sendgrid connect), or dashboard.

3. Recipients + send date — pass allEnrichedLeads: true (or leadIds[]) to create_campaign or vibejoy campaign create --all-leads. No dashboard required.

All three steps work via MCP, CLI, and REST API. A single agent prompt or CLI command can run the full pipeline end to end.

Complete pipeline

1. Setup brand
2. Connect SendGrid
3. Find leads
4. Enrich contacts
5. Create campaign
6. Activate emails
7. Schedule social
8. Monitor

Via MCP — full setup and campaign in one conversation

The agent handles everything. A fresh account goes from zero to live campaign without touching the dashboard:

"Check my VibeJoy setup — is everything ready to run campaigns?"

check_integrations→ shows brand context status, SendGrid status, LinkedIn status

"Set up my brand: business is Acme CRM, we help SDR teams automate outreach, targeting VP Sales at 50-500 person B2B SaaS companies. Tone: Direct, goal: Book calls."

setup_brand→ brand context saved, AI now has your voice

"Connect SendGrid with key SG.xxx, sending from 'Jane at Acme' <jane@acme.com>"

connect_sendgrid→ SendGrid connected, email delivery ready

"Find 25 companies in London actively hiring SDRs"

find_leads→ 25 leads saved with websites and LinkedIn

"Enrich those leads — I need decision-maker emails"

enrich_contacts→ verified emails, job titles, LinkedIn profiles

"Write a campaign for sales leaders at companies scaling their SDR team. Goal: book a 20-minute call. Use all my enriched leads as recipients and activate it."

create_campaign→ 3-email sequence written, all enriched leads added, campaign activated — no dashboard needed

"Show me the social content for that campaign so I can schedule it"

get_campaign_social→ LinkedIn post, DM sequence, Reddit post and reply templates

"What are my campaign stats?"

get_campaignsget_dashboard_stats→ sends, opens, replies

Do it all in one prompt (after initial setup)

"Find 25 funded Series A startups, enrich their contacts, write a campaign to book demos, use all enriched leads as recipients, and activate it."

The agent chains find_leads → enrich_contacts → create_campaign (with allEnrichedLeads + activate) automatically. No dashboard, no manual steps.

Via CLI

# ── ONE-TIME SETUP ──────────────────────────────────────────────────────────

# Set up brand context (required before campaigns)
vibejoy brand setup \
  --name "Acme CRM" \
  --offer "We help SDR teams automate outreach and book more meetings" \
  --icp "VP Sales at 50-500 person B2B SaaS companies" \
  --tone "Direct" \
  --goal "Book calls"

# Connect SendGrid (required before email delivery)
vibejoy sendgrid connect \
  --key "SG.your_sendgrid_key" \
  --from-name "Jane at Acme" \
  --from-email "jane@acme.com"

# Verify everything is ready
vibejoy status

# ── REPEATABLE CAMPAIGN PIPELINE ─────────────────────────────────────────────

# Step 1: Find companies hiring for roles your SaaS automates
vibejoy find --signal hiring --query "RevOps Manager" --location "United Kingdom" --limit 25 --output leads.csv

# Step 2: Enrich — target only the decision-maker you want
vibejoy enrich --unenriched --titles "CEO,Founder,CTO"

# Or filter by seniority level (c_suite, founder, vp, director, manager, owner)
# vibejoy enrich --unenriched --seniority "c_suite,founder"

# Or enrich a specific batch, targeting CFOs only
# vibejoy enrich --file leads.csv --titles "CFO,Finance Director" --output enriched.csv

# Step 3: Create campaign, add all enriched leads, and activate in one command
vibejoy campaign create \
  --audience "RevOps managers scaling their CRM stack" \
  --goal "book a 20-minute product demo" \
  --all-leads \
  --email1-date 2026-07-10 \
  --activate

# Step 4: Get social content (LinkedIn + Reddit) to schedule via Zernio
vibejoy campaign social --id CAMPAIGN_ID --output social.txt

# Step 5: Monitor stats
vibejoy stats
vibejoy campaign list

Via REST API (Node.js)

const BASE = 'http://localhost:3001';
const KEY  = 'vj_your_api_key_here';
const headers = { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json' };

// ── ONE-TIME SETUP ────────────────────────────────────────────────────────────

// Step 0a: Set up brand context (do this once — the AI uses it for all campaigns)
await fetch(`${BASE}/api/brand`, {
  method: 'POST', headers,
  body: JSON.stringify({
    businessName: 'Acme CRM',
    offer: 'We help SDR teams automate outreach and book more meetings',
    icp: 'VP Sales at 50-500 person B2B SaaS companies',
    tone: 'Direct',   // Bold | Warm | Direct | Professional | Conversational
    goal: 'Book calls' // Get leads | Get signups | Build awareness | Book calls
  })
}).then(r => r.json());

// Step 0b: Connect SendGrid (do this once — required for email delivery)
await fetch(`${BASE}/api/integrations/sendgrid/connect`, {
  method: 'POST', headers,
  body: JSON.stringify({
    apiKey: 'SG.your_sendgrid_api_key',
    fromName: 'Jane at Acme',
    fromEmail: 'jane@acme.com'   // must be a verified sender in SendGrid
  })
}).then(r => r.json());

// ── REPEATABLE CAMPAIGN PIPELINE ──────────────────────────────────────────────

// Step 1: Find hiring signal leads
const { leads } = await fetch(`${BASE}/api/leads/intent/jobs`, {
  method: 'POST', headers,
  body: JSON.stringify({ jobTitles: ['RevOps Manager'], location: 'United Kingdom', maxResults: 25 })
}).then(r => r.json());

console.log(`Found ${leads.length} leads`);
const leadIds = leads.map(l => l._id);

// Step 2: Enrich contacts (parallel, streams progress if you use /enrich-stream)
const { enriched, newLeadsCreated } = await fetch(`${BASE}/api/leads/enrich`, {
  method: 'POST', headers,
  body: JSON.stringify({ leadIds })
}).then(r => r.json());

console.log(`Enriched ${enriched} leads, ${newLeadsCreated} extra contacts found`);

// Step 3: Generate + add recipients + activate in one call
// Pass allEnrichedLeads: true to auto-add every enriched lead
// Pass activate: true to go live immediately (requires SendGrid connected)
const { campaignId, name, output, activated, recipientCount } = await fetch(`${BASE}/api/campaigns/generate`, {
  method: 'POST', headers,
  body: JSON.stringify({
    targetAudience: 'RevOps managers scaling their tech stack',
    campaignGoal: 'book a 20-minute product demo',
    extraContext: 'Mention we integrate with their existing CRM',
    allEnrichedLeads: true,                                  // add all enriched leads
    email1At: new Date(Date.now() + 24*60*60*1000).toISOString(), // send tomorrow
    activate: true,                                          // go live immediately
  })
}).then(r => r.json());

console.log(`Campaign: ${name} (ID: ${campaignId})`);
console.log(`Recipients: ${recipientCount} | Activated: ${activated}`);
console.log(`Email 1 subject: ${output.emails?.email1?.subject}`);

// Step 5: Check stats any time
const stats = await fetch(`${BASE}/api/dashboard/stats`, { headers }).then(r => r.json());
console.log(`Total leads: ${stats.totalLeads} | Emails sent: ${stats.totalEmailsSent}`);

Via REST API (Python)

import requests

BASE = "http://localhost:3001"
KEY  = "vj_your_api_key_here"
HEADERS = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}

# ── ONE-TIME SETUP ────────────────────────────────────────────────────────────

# Set up brand context
requests.post(f"{BASE}/api/brand", headers=HEADERS, json={
    "businessName": "Acme CRM",
    "offer": "We help SDR teams automate outreach and book more meetings",
    "icp": "VP Sales at 50-500 person B2B SaaS companies",
    "tone": "Direct",
    "goal": "Book calls"
})

# Connect SendGrid
requests.post(f"{BASE}/api/integrations/sendgrid/connect", headers=HEADERS, json={
    "apiKey": "SG.your_sendgrid_api_key",
    "fromName": "Jane at Acme",
    "fromEmail": "jane@acme.com"
})

# ── REPEATABLE CAMPAIGN PIPELINE ──────────────────────────────────────────────

# Step 1: Find leads
r = requests.post(f"{BASE}/api/leads/intent/jobs",
    headers=HEADERS,
    json={"jobTitles": ["RevOps Manager"], "location": "United Kingdom", "maxResults": 25})
leads = r.json()["leads"]
lead_ids = [l["_id"] for l in leads]
print(f"Found {len(lead_ids)} leads")

# Step 2: Enrich
r = requests.post(f"{BASE}/api/leads/enrich", headers=HEADERS, json={"leadIds": lead_ids})
print(r.json())  # { enriched, newLeadsCreated, failed }

# Step 3: Generate + add all enriched leads + activate in one call
from datetime import datetime, timedelta
tomorrow = (datetime.utcnow() + timedelta(days=1)).isoformat() + "Z"

r = requests.post(f"{BASE}/api/campaigns/generate", headers=HEADERS, json={
    "targetAudience": "RevOps managers scaling their tech stack",
    "campaignGoal": "book a 20-minute product demo",
    "allEnrichedLeads": True,   # auto-add every enriched lead as recipient
    "email1At": tomorrow,        # send tomorrow
    "activate": True             # go live immediately (requires SendGrid)
})
data = r.json()
print(f"Campaign: {data['name']} | Recipients: {data['recipientCount']} | Live: {data['activated']}")

Daily Lead Pipeline — Find CEOs, CFOs & Decision-Makers Automatically

Set VibeJoy to run on a schedule so it finds new leads every day, enriches only the ones it hasn't seen before, and drips them into a live campaign — all without you touching anything.

How it works — the daily loop

Scheduler triggers
Find new companies
Skip duplicates (auto)
Enrich CEOs/CFOs only
Add to live campaign
Emails go out on schedule

Duplicate detection is built in — if VibeJoy finds a company it already has, it skips it. Only genuinely new leads are enriched and added.

Option 1 — CLI + cron (simplest for developers)

Save this as a shell script and schedule it with cron (Linux/macOS) or Task Scheduler (Windows).

vibejoy-daily.sh
#!/bin/bash
# Daily VibeJoy pipeline — finds CEOs/CFOs at companies hiring for relevant roles,
# enriches new ones, and adds them to a live campaign.
# Schedule: run daily at 7am with cron: 0 7 * * * /path/to/vibejoy-daily.sh

set -e  # stop on any error

echo "[$(date)] Starting daily VibeJoy pipeline..."

# ── Step 1: Find new companies (duplicates are skipped automatically) ──────────
vibejoy find \
  --signal hiring \
  --query "CFO OR Chief Financial Officer OR Finance Director" \
  --location "United Kingdom" \
  --limit 25

vibejoy find \
  --signal hiring \
  --query "CEO OR Chief Executive OR Managing Director" \
  --location "United States" \
  --limit 25

# ── Step 2: Enrich only leads not yet enriched, targeting CFOs/CEOs ────────────
vibejoy enrich \
  --unenriched \
  --titles "CEO,CFO,Chief Executive,Chief Financial Officer,Managing Director,Finance Director" \
  --seniority "c_suite,founder"

# ── Step 3: Add new enriched leads to the existing live campaign ───────────────
# Replace CAMPAIGN_ID with your actual campaign ID (vibejoy campaign list)
vibejoy campaign activate \
  --id CAMPAIGN_ID \
  --all-leads

echo "[$(date)] Pipeline complete."

Make it executable and schedule it:

# Make executable
chmod +x vibejoy-daily.sh

# Schedule via cron (Linux/macOS) — runs every day at 7am
crontab -e
# Add this line:
0 7 * * * /path/to/vibejoy-daily.sh >> /var/log/vibejoy.log 2>&1

# Windows Task Scheduler — create a Basic Task:
# Program: node
# Arguments: C:\path\to\vibejoy-daily.sh
# Trigger: Daily at 7:00 AM

Option 2 — GitHub Actions (zero-infrastructure scheduling)

No server needed. GitHub runs the workflow on your schedule for free on public repos, and free minutes on private repos are generous.

.github/workflows/daily-leads.yml
name: Daily Lead Discovery

on:
  schedule:
    - cron: '0 7 * * 1-5'   # 7am UTC, Monday to Friday
  workflow_dispatch:          # also allows manual trigger from GitHub UI

jobs:
  discover:
    runs-on: ubuntu-latest
    steps:
      - name: Install VibeJoy CLI
        run: npm install -g vibejoy-cli   # or: npm install -g .
      
      - name: Authenticate
        run: vibejoy auth --key ${{ secrets.VIBEJOY_API_KEY }}
        env:
          VIBEJOY_API_KEY: ${{ secrets.VIBEJOY_API_KEY }}

      - name: Find CEOs at companies hiring senior roles
        run: |
          vibejoy find --signal hiring --query "CEO OR Chief Executive" --location "United Kingdom" --limit 20
          vibejoy find --signal hiring --query "CFO OR Finance Director" --location "United States" --limit 20

      - name: Enrich only new leads (CEOs and CFOs only)
        run: |
          vibejoy enrich --unenriched \
            --titles "CEO,CFO,Chief Executive,Finance Director" \
            --seniority "c_suite,founder"

      - name: Add to campaign and re-activate
        run: vibejoy campaign activate --id ${{ secrets.CAMPAIGN_ID }} --all-leads
        env:
          CAMPAIGN_ID: ${{ secrets.CAMPAIGN_ID }}

GitHub Secrets to add

In your GitHub repo: Settings → Secrets → New repository secret

  • VIBEJOY_API_KEY — your vj_ key from Settings
  • CAMPAIGN_ID — the campaign ID to keep feeding (run vibejoy campaign list to find it)

Option 3 — REST API + any scheduler (cron-job.org, Zapier, n8n)

If you don't want to run code, use a free HTTP scheduler like cron-job.org to hit the API directly. Chain calls in order:

// Step 1: Find new companies hiring for senior roles (run daily)
// VibeJoy automatically skips companies it already has — safe to run every day
const findRes = await fetch('https://your-api.com/api/leads/intent/jobs', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jobTitles: ['CEO', 'CFO', 'Chief Financial Officer', 'Managing Director'],
    location: 'United Kingdom',
    maxResults: 25
  })
}).then(r => r.json());

console.log(`Found ${findRes.count} new leads, skipped ${findRes.skippedDuplicates} duplicates`);

// Step 2: Enrich only unenriched leads — target CEO/CFO titles specifically
const enrichRes = await fetch('https://your-api.com/api/leads/enrich', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    unenrichedOnly: true,
    titles: ['CEO', 'CFO', 'Chief Executive Officer', 'Chief Financial Officer', 'Managing Director', 'Finance Director'],
    seniority: ['c_suite', 'founder']
  })
}).then(r => r.json());

console.log(`Enriched ${enrichRes.enriched} contacts`);

// Step 3: Add all enriched leads to a live campaign (safe to re-run — no duplicates added)
const updateRes = await fetch('https://your-api.com/api/campaigns/CAMPAIGN_ID', {
  method: 'PATCH',
  headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({ allEnrichedLeads: true })
}).then(r => r.json());

console.log(`Campaign now has ${updateRes.recipientCount} recipients`);

Option 4 — MCP / AI Agent (conversational recurring prompt)

Paste this prompt in Claude or Cursor each morning, or schedule it with an agent runner:

Daily agent prompt — copy and run each morning

Run my daily lead pipeline: 1. Find 25 companies in the UK actively hiring for CEO, CFO, or Finance Director roles — use the find_leads tool with signal: hiring 2. Find 25 more companies in the US hiring Managing Directors or Chief Executives 3. Enrich only the leads that haven't been enriched yet — target contacts with title CEO, CFO, or Managing Director, seniority c_suite 4. Add all newly enriched leads to campaign ID: YOUR_CAMPAIGN_ID (use create_campaign with allEnrichedLeads: true if the campaign doesn't exist yet, otherwise PATCH the existing one) 5. Tell me: how many new companies were found, how many were skipped as duplicates, how many contacts were enriched, and the total recipient count on the campaign.

"Find 25 UK companies hiring CEOs or CFOs — skip any I already have"

find_leads→ 18 new leads, 7 skipped (already in your list)

"Enrich the new unenriched leads — I only want CEO or CFO contacts"

enrich_contacts→ 14 contacts enriched with decision-maker emails

"Add all enriched leads to my outreach campaign and give me the new recipient count"

create_campaignget_campaigns→ campaign updated, 47 total recipients

Option 5 — n8n workflow (full no-code automation)

Build this in n8n and schedule it with the Cron trigger node. Set to run Monday-Friday at 7am.

Cron (daily 7am)
HTTP: find leads (jobs)
HTTP: enrich (CEO/CFO)
HTTP: PATCH campaign
Slack: daily summary
1. Cron triggerSchedule: 0 7 * * 1-5 (Mon-Fri 7am)
2. HTTP Request — find leadsPOST /api/leads/intent/jobs · body: { jobTitles: ["CEO","CFO"], location: "UK", maxResults: 25 }
3. IF — skip if count = 0{{ $json.count > 0 }} — stops if no new leads found
4. HTTP Request — enrichPOST /api/leads/enrich · body: { unenrichedOnly: true, titles: ["CEO","CFO"], seniority: ["c_suite"] }
5. HTTP Request — update campaignPATCH /api/campaigns/CAMPAIGN_ID · body: { allEnrichedLeads: true }
6. Slack message"Daily pipeline: {{ $node[2].json.count }} new leads, {{ $node[4].json.enriched }} enriched, {{ $node[5].json.recipientCount }} in campaign"

Tips for a healthy daily pipeline

  • Duplicate detection is automatic — running the same find query daily is safe. VibeJoy skips companies it already has and tells you how many were skipped (skippedDuplicates in the response).
  • Watch your monthly lead quota — check the usage page or run vibejoy stats to see how many leads you've used this month.
  • Vary your queries — rotate job titles and locations week by week to reach different segments without exhausting one source.
  • Use a "drip" campaign — keep one evergreen campaign active and keep adding enriched leads to it. Email 1 fires 24h after a lead is added, emails 2 and 3 follow automatically.
  • Start small — 10-15 leads per day is a better starting point than 50. Quality over volume when warming up a sending domain.

SendGrid Setup

SendGrid delivers your campaign emails. You connect your own account so emails come from your domain with your sender reputation — not a shared VibeJoy pool. Setup takes about 5 minutes. You can connect multiple accounts (different domains or different clients) and choose which one each campaign sends from.

SendGrid free tier

The free plan includes 100 emails/day with no credit card required. That covers most early-stage outreach campaigns. Paid plans start at $19.95/month for higher volumes.

Step 1 — Create a SendGrid account

Go to sendgrid.com and sign up. No credit card needed for the free plan.

Step 2 — Verify your sender identity

This is the address that appears in the "From" field of every email. SendGrid will reject any send from an unverified address — this causes silent failures.

In SendGrid: go to Settings → Sender Authentication. Choose one of:

A

Single Sender Verification — quickest (2 min)

Click Verify a Single Sender, fill in your name, email, and company. SendGrid sends you a confirmation email. Click the link. Done. Best for getting started fast.

B

Domain Authentication — better deliverability (10 min)

Adds SPF + DKIM DNS records to your domain. Emails send from you@yourdomain.com with full authentication. Significantly improves inbox placement. Recommended if you have access to your DNS settings.

In SendGrid: Settings → Sender Authentication → Authenticate Your Domain. Follow the wizard — it generates the exact DNS records to add.

Important: If you skip sender verification, emails will silently fail to send. Always verify before connecting to VibeJoy.

Step 3 — Create a SendGrid API key

In SendGrid: Settings → API Keys → Create API Key.

Name it anything (e.g. "VibeJoy")

Choose Restricted Access

Enable Mail Send → Full Access

Enable Suppressions → Read Access (for managing unsubscribes)

Click Create & View and copy the key immediately — it starts with SG. and is shown only once

Step 4 — Connect to VibeJoy

Connect via dashboard, MCP, CLI, or REST API. All methods are equivalent.

Via Dashboard (easiest)

Go to Settings → Integrations → SendGrid. Fill in the form:

Account labelA name for this account, e.g. "Main domain" or "Client A" — useful when you have multiple accounts
API keyPaste the SG. key you just copied
From nameWhat appears in the recipient's inbox — e.g. "Jane at Acme" or "Acme Team"
From emailMust match a verified sender in SendGrid — e.g. jane@acme.com

Via MCP (ask your agent)

"Connect SendGrid with key SG.your_key, sending from 'Jane at Acme' <jane@acme.com>"

connect_sendgrid→ validates key, saves account, email delivery ready

Via CLI

vibejoy sendgrid connect \
  --key "SG.your_sendgrid_api_key" \
  --from-name "Jane at Acme" \
  --from-email "jane@acme.com"

# Verify it worked
vibejoy status
# ✅ SendGrid email — connected, sending from jane@acme.com

Via REST API

curl -X POST https://your-api.com/api/integrations/sendgrid/connect \
  -H "Authorization: Bearer vj_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "SG.your_sendgrid_api_key",
    "fromName": "Jane at Acme",
    "fromEmail": "jane@acme.com"
  }'

Multiple SendGrid accounts (multi-domain or agency use)

You can connect multiple SendGrid accounts — useful if you have different sending domains (e.g. outreach@acme.com and team@acme.io) or if you manage campaigns for different clients.

Adding a second account (Dashboard)

Go to Settings → Integrations → SendGrid and click + Add account. Fill in the label, API key, from name, and from email for the new domain. Each account can have its own API key.

Adding accounts via REST API

# List all connected accounts
curl https://your-api.com/api/integrations/sendgrid/accounts \
  -H "Authorization: Bearer vj_your_api_key"
# → { accounts: [{ _id, label, fromName, fromEmail, isDefault }, ...] }

# Add a second account (different domain)
curl -X POST https://your-api.com/api/integrations/sendgrid/accounts \
  -H "Authorization: Bearer vj_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "EU domain",
    "apiKey": "SG.another_key",
    "fromName": "Acme Europe",
    "fromEmail": "hello@acme.eu"
  }'

# Remove an account
curl -X DELETE https://your-api.com/api/integrations/sendgrid/accounts/ACCOUNT_ID \
  -H "Authorization: Bearer vj_your_api_key"

# Set a different default sender
curl -X PATCH https://your-api.com/api/integrations/sendgrid/accounts/ACCOUNT_ID/default \
  -H "Authorization: Bearer vj_your_api_key"

Selecting sender per campaign

When creating a campaign in the dashboard, a Send from dropdown appears on the schedule step if you have more than one account — pick which one to use. Via REST API, pass sendgridAccountId in the campaign PATCH:

curl -X PATCH https://your-api.com/api/campaigns/CAMPAIGN_ID \
  -H "Authorization: Bearer vj_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "sendgridAccountId": "ACCOUNT_ID"
  }'
# Campaigns default to your primary/default account if not specified

Deliverability tips

  • Use domain authentication rather than single sender if possible — it significantly improves inbox placement
  • Start with 20-50 emails per day and ramp up gradually to warm your domain
  • Each sending domain (different fromEmail) should be separately verified in SendGrid
  • Never send to unverified or purchased lists — this damages your sender reputation
  • Add an unsubscribe link to stay CAN-SPAM and GDPR compliant — VibeJoy includes this automatically

Social Scheduling (LinkedIn + Reddit)

Every campaign VibeJoy generates includes ready-to-schedule social content. The AI writes conversation-starting copy — not pitches — designed to open genuine discussions with your ICP. You review it, then schedule it.

What gets generated for every campaign

LinkedIn post

150-200 words, brand voice, observation-led — no hard sell, max 3 hashtags

LinkedIn DM sequence

Connection request note + 3 follow-up DMs spaced over 2 weeks

Reddit original post

Value-first post for your ICP subreddit — adds insight, does not pitch

Reddit reply templates

2 ready-to-use replies for relevant threads in your subreddit

Part A — Get your social content

After creating a campaign, retrieve the social content via your preferred interface:

Dashboard

Go to Campaigns → open your campaign → Social tab. You'll see the LinkedIn post, DM sequence, Reddit post, and reply templates — each with a copy button.

MCP (ask your agent)

"Show me the social content for my latest campaign — the LinkedIn post, DMs, and Reddit copy"

get_campaignsget_campaign_social→ returns all social copy, ready to review and schedule

CLI

# Print to terminal
vibejoy campaign social --id CAMPAIGN_ID

# Or save to a file (recommended for long content)
vibejoy campaign social --id CAMPAIGN_ID --output social.txt

# Find your campaign ID first if needed
vibejoy campaign list

REST API

curl https://your-api.com/api/campaigns/CAMPAIGN_ID \
  -H "Authorization: Bearer vj_your_api_key"

# Response shape — campaign.output contains:
{
  "linkedinPost": "...",           // the LinkedIn post text
  "linkedinDMs": {
    "connectionRequest": "...",    // 280 char max — sent with the connection invite
    "dm1": "...",                  // send 2-3 days after they accept
    "dm2": "...",                  // send 5-7 days after dm1
    "dm3": "..."                   // send 10-14 days after dm2 (graceful exit if no reply)
  },
  "reddit": {
    "originalPost": {
      "subreddit": "r/sales",      // suggested subreddit
      "title": "...",
      "body": "..."
    },
    "replyTemplates": [
      { "scenario": "...", "body": "..." },
      { "scenario": "...", "body": "..." }
    ]
  }
}

Part B — Connect Zernio for LinkedIn scheduling

Zernio is the service that handles LinkedIn post and DM scheduling from VibeJoy. You connect it once and it stays connected.

Zernio is optional. If you prefer to copy and paste content manually into LinkedIn or another scheduler, you can skip this. Social content is always available in the dashboard Social tab.

1

Create a Zernio account

Go to zernio.io and sign up for a free account.

2

Connect your LinkedIn profile in Zernio

Once logged in to Zernio:

  1. Click Connections in the left sidebar
  2. Find LinkedIn and click + Connect
  3. A LinkedIn OAuth popup appears — log in and grant access
  4. Your LinkedIn profile now appears in the Connections list with a green "Connected" status
3

Create your Zernio API key

VibeJoy uses your own Zernio API key to schedule on your behalf — so billing, rate limits, and LinkedIn authentication all stay within your account.

  1. In Zernio, go to Settings → API Keys
  2. Click Create API Key and give it a name (e.g. "VibeJoy")
  3. Copy the key — it starts with sk_ and is shown only once

Important: Copy it immediately — Zernio only shows the full key once. If you lose it, delete it and create a new one.

4

Copy your Zernio Account ID

This tells VibeJoy which LinkedIn profile to post from. Here is exactly where to find it:

1

Go to the Connections page in Zernio

2

Look for the long alphanumeric string next to your LinkedIn profile name — it looks like: 6a58876227846c0757346174

3

Click the copy icon next to it to copy the full string — that is your Account ID

Tip: The Zernio dashboard URL often contains your Account ID — e.g. zernio.io/dashboard/6a58876227846c0757346174. The ID in the URL is your Account ID.

5

Paste both into VibeJoy Settings

Go to Settings → Integrations → LinkedIn via Zernio:

  1. Paste your Zernio API key (sk_...) into the first field
  2. Paste your Account ID into the second field
  3. Click Connect LinkedIn
  4. VibeJoy validates both and saves them encrypted — you'll see "Connected: Your Name"

Done. VibeJoy now schedules LinkedIn posts through your own Zernio account. You only do this once.

Part C — Schedule your content

LinkedIn post

Copy the LinkedIn post text from the campaign Social tab. In Zernio, create a new Post, paste the copy, and set a publish time.

Best times for B2B LinkedIn posts

Tuesday, Wednesday, Thursday — 8am to 10am your audience's timezone. Avoid weekends and Monday mornings.

LinkedIn DM sequence

The DM sequence has four parts. Send them manually or through a LinkedIn automation tool:

1

Connection request

Send with the connection inviteMax 280 characters — VibeJoy keeps it short and personal

2

Message 1 — value drop

2-3 days after they acceptShare a useful observation, no ask yet

3

Message 2 — soft ask

5-7 days after Message 1Gentle question or invitation to chat

4

Message 3 — graceful exit

10-14 days after Message 2 (no reply)Close the loop warmly, leave the door open

For fully automated DM sequences, tools like Expandi, Waalaxy, or Dripify can send the full sequence automatically. Copy the four messages from VibeJoy into their sequence builder.

Reddit

Original post

The AI suggests a subreddit based on your ICP. Go to that subreddit, verify the rules allow discussion posts (most do), and submit. The post adds value first — it does not pitch your product directly.

Reply templates

Search the subreddit for threads matching the scenario in each reply template (e.g. "struggling with SDR ramp time"). When you find one, paste the reply and edit it to match the specific thread context. Never paste unedited — Reddit readers spot templates immediately.

Reddit self-promotion rules

Most subreddits allow 1 self-promotional post for every 9 community contributions (the 9:1 rule). VibeJoy's posts are written to be value-first and community-friendly, but always check the subreddit sidebar for their specific rules before posting.

End-to-end via MCP — one conversation

Once Zernio is connected, a single agent conversation handles everything from lead discovery to content retrieval:

"Find 20 companies hiring SDRs in London, enrich their contacts targeting VP Sales or Head of Sales, write a campaign, and activate it"

find_leadsenrich_contactscreate_campaign→ leads found, enriched, campaign written and activated

"Show me the social content for that campaign so I can schedule it"

get_campaign_social→ LinkedIn post + DM sequence + Reddit post + reply templates

"What are my dashboard stats — how many emails have been sent?"

get_dashboard_stats→ leads, campaigns, emails sent, opens

Authentication

All API requests require an API key. Generate one from Settings → Developer.

Pass your key in the Authorization header on every request:

Authorization: Bearer vj_your_api_key_here

Keep your key secret

Never commit it to source control. Store it in an environment variable. If it's leaked, regenerate immediately from Settings.

Limits by plan

PlanAPI calls/monthMCPCLIWebhooksSSE streaming
Free1000
Starter2,0002
Growth10,00010
ProUnlimitedUnlimited

MCP Server

The VibeJoy MCP server exposes your full lead engine as tools that any MCP-compatible AI can call — Claude Desktop, Cursor Agent, or any app that speaks the Model Context Protocol.

Setup

Cursor (global config — recommended)

~/.cursor/mcp.json
{
  "mcpServers": {
    "vibejoy": {
      "command": "node",
      "args": ["/absolute/path/to/vibejoy/mcp/index.js"],
      "env": {
        "VIBEJOY_API_KEY": "vj_your_api_key_here",
        "VIBEJOY_API_URL": "http://localhost:3001"
      }
    }
  }
}

Reload the Cursor window (Ctrl+Shift+P → Reload Window) after saving.

Claude Desktop (macOS)

~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "vibejoy": {
      "command": "node",
      "args": ["/absolute/path/to/vibejoy/mcp/index.js"],
      "env": {
        "VIBEJOY_API_KEY": "vj_your_api_key_here",
        "VIBEJOY_API_URL": "http://localhost:3001"
      }
    }
  }
}

Claude Desktop (Windows)

%APPDATA%\\Claude\\claude_desktop_config.json
{
  "mcpServers": {
    "vibejoy": {
      "command": "node",
      "args": ["C:\\path\\to\\vibejoy\\mcp\\index.js"],
      "env": {
        "VIBEJOY_API_KEY": "vj_your_api_key_here",
        "VIBEJOY_API_URL": "http://localhost:3001"
      }
    }
  }
}

Available tools

check_integrations

Check your setup status: whether brand context is configured, SendGrid is connected, and LinkedIn is linked. Run this first to confirm everything is ready before creating a campaign.

All
(none)
setup_brand

Set your brand context — business name, offer, ICP, tone, and campaign goal. The AI uses this as its voice for every campaign. Must be completed before campaign generation.

All
businessName: string
offer: string
icp: string (ideal customer profile)
tone: "Bold" | "Warm" | "Direct" | "Professional" | "Conversational"
goal: "Get leads" | "Get signups" | "Build awareness" | "Book calls"
connect_sendgrid

Connect a SendGrid account for email delivery. Required before campaigns can send emails. The sender email must be verified in your SendGrid account. You can call this tool multiple times to add multiple accounts (different domains or clients) — they all appear in Settings and can be selected per campaign.

All
apiKey: string (starts with SG.)
fromName: string (display name in From field)
fromEmail: string (verified sender address)
label?: string (e.g. "Sales domain" or "Client A" — helps identify the account)
find_leads

Find leads by intent signal type. Returns leads saved to your database with _id fields.

Starter
source: "local" | "job_board" | "reddit" | "funding" | "linkedin_post"
query: string (keyword, job title, pain phrase, funding stage)
location?: string (city/country — for local and job_board)
limit?: number (default 25, max 100)
enrich_contacts

Enrich leads with verified emails, job titles, LinkedIn profiles, and phone numbers. Returns counts of enriched leads and any new contacts discovered at the same company.

Starter
leadIds: string[] — array of _id values returned by find_leads
create_campaign

Generate an AI-written email sequence in your brand voice. Optionally activate immediately for SendGrid delivery.

Starter
targetAudience: string — who the campaign targets
campaignGoal: string — desired outcome (e.g. "book a demo")
extraContext?: string — any extra context for the AI
activate?: boolean — true to activate immediately (default false)
get_campaigns

List all your campaigns with status, email sequences, reply rate, and delivery stats. No parameters required.

All
(none)
get_dashboard_stats

Fetch this month's usage: total leads, enriched leads, campaigns created, emails sent, LinkedIn sent. No parameters required.

All
(none)
get_brand_context

Read your current brand context: business name, offer, ideal customer profile, tone, and campaign goal. Used as the AI's voice for all campaign generation.

All
(none)

Example agent conversation

This is a real transcript of what happens when you talk to Cursor Agent with VibeJoy connected:

"What's my brand context?"

get_brand_context→ shows your business name, ICP, offer, tone

"Find 20 SaaS companies that just raised a Series A"

find_leads (source: funding, query: series-a)→ 20 leads saved with websites

"Enrich them — I need decision-maker emails"

enrich_contacts→ verified emails + titles via B2B database

"Write a 3-email campaign targeting their new CTO/VP Eng. Goal is a product demo."

create_campaign→ AI writes in your brand voice

"Looks good. Activate it."

create_campaign (activate: true)→ queued in SendGrid, starts sending

"How many emails have we sent this month?"

get_dashboard_stats→ full usage breakdown

AI Agents

Beyond MCP, you can use VibeJoy's REST API directly from any AI agent framework — LangChain, LlamaIndex, OpenAI function calling, CrewAI, or your own custom agent loop.

Cursor Agent mode

With the MCP configured, Cursor's Agent mode can run your entire outbound pipeline autonomously. Just describe what you want:

Cursor Agent prompt

"Every Monday morning, find 30 companies that are actively hiring for the role my SaaS automates. Enrich them. Check if we already have a campaign targeting this audience — if not, write a new one and activate it. Then update a Notion table with this week's campaign stats."

→ Agent calls: get_campaigns → find_leads → enrich_contacts → create_campaign → (Notion MCP)

OpenAI function calling

import OpenAI from 'openai';
import fetch from 'node-fetch';

const openai = new OpenAI();
const VJ_KEY = process.env.VIBEJOY_API_KEY;
const VJ_BASE = 'http://localhost:3001';
const h = { Authorization: `Bearer ${VJ_KEY}`, 'Content-Type': 'application/json' };

const tools = [
  {
    type: 'function',
    function: {
      name: 'find_leads',
      description: 'Find leads by intent signal',
      parameters: {
        type: 'object',
        properties: {
          source: { type: 'string', enum: ['job_board','reddit','funding','linkedin_post','local'] },
          query:  { type: 'string' },
          location: { type: 'string' },
          limit: { type: 'number' }
        },
        required: ['source','query']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'enrich_contacts',
      description: 'Enrich leads with verified emails, job titles, and LinkedIn. Filter by titles or seniority.',
      parameters: {
        type: 'object',
        properties: {
          leadIds: { type: 'array', items: { type: 'string' } },
          titles: { type: 'array', items: { type: 'string' }, description: 'Job title keywords e.g. ["CEO","Founder"]' },
          seniority: { type: 'array', items: { type: 'string' }, description: 'Seniority levels e.g. ["c_suite","vp"]' }
        },
        required: ['leadIds']
      }
    }
  }
];

async function callVibeJoy(name, args) {
  if (name === 'find_leads') {
    const res = await fetch(`${VJ_BASE}/api/leads/intent/jobs`, {
      method: 'POST', headers: h,
      body: JSON.stringify({ jobTitles: [args.query], location: args.location, maxResults: args.limit || 25 })
    });
    return res.json();
  }
  if (name === 'enrich_contacts') {
    const res = await fetch(`${VJ_BASE}/api/leads/enrich`, {
      method: 'POST', headers: h,
      body: JSON.stringify({ leadIds: args.leadIds, titles: args.titles || [], seniority: args.seniority || [] })
    });
    return res.json();
  }
}

// Agent loop
let messages = [{ role: 'user', content: 'Find 10 companies hiring sales ops in London, then enrich them.' }];

while (true) {
  const res = await openai.chat.completions.create({ model: 'gpt-4o', tools, messages });
  const msg = res.choices[0].message;
  messages.push(msg);

  if (res.choices[0].finish_reason === 'stop') {
    console.log(msg.content);
    break;
  }

  for (const call of msg.tool_calls || []) {
    const result = await callVibeJoy(call.function.name, JSON.parse(call.function.arguments));
    messages.push({ role: 'tool', tool_call_id: call.id, content: JSON.stringify(result) });
  }
}

LangChain agent

from langchain.agents import create_openai_functions_agent, AgentExecutor
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
import requests, os

BASE = "http://localhost:3001"
H = {"Authorization": f"Bearer {os.environ['VIBEJOY_API_KEY']}", "Content-Type": "application/json"}

@tool
def find_leads(source: str, query: str, location: str = "", limit: int = 25) -> dict:
  """Find leads by intent signal. source: job_board | reddit | funding | linkedin_post | local"""
  r = requests.post(f"{BASE}/api/leads/intent/jobs", headers=H,
    json={"jobTitles": [query], "location": location, "maxResults": limit})
  return r.json()

@tool
def enrich_contacts(lead_ids: list[str]) -> dict:
  """Enrich leads with verified email, title, and LinkedIn"""
  r = requests.post(f"{BASE}/api/leads/enrich", headers=H, json={"leadIds": lead_ids})
  return r.json()

@tool
def get_dashboard_stats() -> dict:
  """Get this month's usage stats"""
  return requests.get(f"{BASE}/api/dashboard/stats", headers=H).json()

llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_functions_agent(llm, [find_leads, enrich_contacts, get_dashboard_stats])
executor = AgentExecutor(agent=agent, tools=[find_leads, enrich_contacts, get_dashboard_stats], verbose=True)

executor.invoke({"input": "Find 20 companies hiring RevOps managers in the UK and enrich them"})

Real-time enrichment progress (SSE)

Use the streaming endpoint to get live progress events during enrichment — useful in agent UIs, dashboards, or long-running scripts.

// Node.js: stream enrichment progress
const response = await fetch('http://localhost:3001/api/leads/enrich-stream', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer vj_your_key', 'Content-Type': 'application/json' },
  body: JSON.stringify({ leadIds: [...] })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  for (const line of decoder.decode(value).split('\n')) {
    if (!line.startsWith('data: ')) continue;
    const evt = JSON.parse(line.slice(6));

    if (evt.type === 'progress') {
      console.log(`[${evt.enriched + evt.failed}/${evt.total}] ${evt.leadName}`);
    }
    if (evt.type === 'done') {
      console.log(`Done: ${evt.enriched} enriched, ${evt.newLeadsCreated} new contacts, ${evt.failed} not found`);
    }
  }
}

CLI

The VibeJoy CLI lets you run lead discovery from your terminal, pipe output into other tools, and integrate VibeJoy into scripts and CI workflows.

Install & authenticate

npm install -g vibejoy
vibejoy auth --key vj_your_api_key_here
vibejoy auth --show   # verify stored key

Commands

vibejoy find

Find intent-signal leads. Flags: --signal (hiring|reddit|funding|linkedin|local), --query, --location, --limit, --output (csv path)

Starter
vibejoy enrich

Enrich leads. --unenriched enriches every lead not yet enriched. --titles "CEO,CTO" targets specific job title keywords. --seniority "c_suite,founder" filters by seniority level. Also accepts --file (csv), --ids, --output (csv).

Starter
vibejoy campaign create

Create an AI campaign from a leads CSV. Flags: --leads, --name, --audience, --goal, --activate

Starter
vibejoy campaign list

List all campaigns with status, sends, and reply rate

All
vibejoy campaign activate

Activate a draft campaign. Flags: --id

Starter
vibejoy campaign pause

Pause an active campaign. Flags: --id

Starter
vibejoy stats

Print this month's usage stats: leads, enriched, campaigns, sends, replies

All
vibejoy leads list

List saved leads. Flags: --enriched (true|false), --limit, --output

All
vibejoy auth

Manage your API key. Flags: --key, --show, --revoke

All

Full pipeline examples

# --- Hiring signal pipeline ---
# Find companies hiring for the role your SaaS automates
vibejoy find --signal hiring --query "Revenue Operations Manager" --location "United Kingdom" --limit 50 --output /tmp/leads.csv

# Enrich all leads not yet enriched (simplest — no file needed)
vibejoy enrich --unenriched

# Or enrich a specific CSV file
# vibejoy enrich --file /tmp/leads.csv --output /tmp/enriched.csv

# Generate and activate campaign with all enriched leads
vibejoy campaign create \
  --audience "RevOps managers scaling their CRM stack" \
  --goal "book a 20-minute product demo" \
  --all-leads \
  --email1-date 2026-07-15 \
  --activate

# --- Pain signal pipeline ---
# Find people on Reddit expressing your customer's pain
vibejoy find --signal reddit --query "cold email not getting replies" --limit 30 --output /tmp/pain.csv

# Enrich all unenriched leads
vibejoy enrich --unenriched

# Create and activate campaign
vibejoy campaign create \
  --audience "founders frustrated with cold email deliverability" \
  --goal "start a conversation about outbound consistency" \
  --all-leads \
  --email1-date 2026-07-15 \
  --activate

# --- Check results ---
vibejoy stats
vibejoy campaign list

Cron / CI integration

# .github/workflows/weekly-leads.yml
name: Weekly lead run
on:
  schedule:
    - cron: '0 8 * * 1'   # Monday 8am UTC

jobs:
  find-and-enrich:
    runs-on: ubuntu-latest
    steps:
      - run: npm install -g vibejoy
      - run: vibejoy auth --key ${VIBEJOY_API_KEY}
        env:
          VIBEJOY_API_KEY: ${{ secrets.VIBEJOY_API_KEY }}
      - run: |
          EMAIL_DATE=$(date -u -d "+2 days" +%Y-%m-%d 2>/dev/null || date -u -v+2d +%Y-%m-%d)
          vibejoy find --signal hiring --query "SDR" --limit 50 --output leads.csv
          vibejoy enrich --unenriched
          vibejoy campaign create \
            --audience "Sales managers scaling outbound without growing headcount" \
            --goal "start a conversation about pipeline efficiency" \
            --all-leads \
            --email1-date "$EMAIL_DATE" \
            --activate
      - run: vibejoy stats

REST API

Base URL: http://localhost:3001 (self-hosted) or https://api.vibejoy.io (cloud)

All endpoints accept and return JSON. Authenticate with Authorization: Bearer vj_...

Setup (run once)

POST
/api/brand

Set brand context — required before campaign generation

{ "businessName": "...", "offer": "...", "icp": "...", "tone": "Direct", "goal": "Book calls" }

All
GET
/api/brand

Read current brand context

All
POST
/api/integrations/sendgrid/connect

Connect primary SendGrid account (legacy — single account)

{ "apiKey": "SG.xxx", "fromName": "Jane at Acme", "fromEmail": "jane@acme.com" }

All
DELETE
/api/integrations/sendgrid/disconnect

Disconnect primary SendGrid account

All
GET
/api/integrations/sendgrid/accounts

List all connected SendGrid accounts (primary + additional)

All
POST
/api/integrations/sendgrid/accounts

Add an additional SendGrid account (different domain or client)

{ "apiKey": "SG.xxx", "fromName": "...", "fromEmail": "...", "label": "Sales domain" }

All
DELETE
/api/integrations/sendgrid/accounts/:id

Remove a SendGrid account by its ID

All
PATCH
/api/integrations/sendgrid/accounts/:id/default

Set a SendGrid account as the default sender

All
POST
/api/integrations/zernio/connect

Connect LinkedIn via your own Zernio account — requires your API key (sk_...) from Settings → API Keys, and Account ID from the Connections page, both at zernio.io

{ "apiKey": "sk_your_zernio_api_key", "accountId": "6a58876227846c0757346174" }

Growth
DELETE
/api/integrations/zernio/disconnect

Disconnect Zernio / LinkedIn scheduling

Growth

Lead discovery

POST
/api/leads/scrape

Local business search — find businesses by keyword and location

{ "keyword": "accountants", "location": "Edinburgh", "maxResults": 50 }

All
POST
/api/leads/intent/jobs

Hiring signals — companies actively recruiting

{ "jobTitles": ["SDR", "RevOps"], "location": "London", "maxResults": 25 }

Starter
POST
/api/leads/intent/reddit

Pain signals — Reddit posts expressing your customer's problem

{ "keywords": "cold email deliverability", "subreddits": ["sales"], "maxResults": 25 }

Starter
POST
/api/leads/intent/funding

Funding signals — recently funded startups with budget to spend

{ "stages": ["seed", "series-a"], "maxResults": 25 }

Growth
POST
/api/leads/intent/linkedin-posts

LinkedIn demand signals — posts about your category

{ "keywords": "outbound sales automation", "maxResults": 25 }

Growth
POST
/api/leads/search-people

Direct people search by title, company, industry, and location

{ "jobTitle": "VP Sales", "location": "London", "companySize": "11-50" }

Starter

Enrichment

POST
/api/leads/enrich

Enrich multiple leads in parallel (up to 6 concurrent). Returns when all complete.

{ "leadIds": ["id1", "id2"] }

Starter
POST
/api/leads/enrich-stream

Same as /enrich but streams Server-Sent Events. Each event: { type, enriched, failed, total, leadName }.

{ "leadIds": ["id1", "id2"] }

Starter
POST
/api/leads/enrich-single

Enrich one lead, returns updated doc immediately.

{ "leadId": "id1" }

Starter

Leads management

GET
/api/leads

Paginated list. Query params: page, limit, status, enriched (true|false)

All
PATCH
/api/leads/:id

Update editable fields on a lead

{ "website": "...", "email": "...", "contactName": "..." }

All
DELETE
/api/leads/:id

Delete a single lead

All

Campaigns

POST
/api/campaigns/generate

Generate AI-written email sequence in your brand voice

{ "targetAudience": "...", "campaignGoal": "...", "extraContext": "..." }

Starter
GET
/api/campaigns

List all campaigns with status, email sequences, and delivery stats

All
GET
/api/campaigns/:id

Single campaign — full emails, subject lines, and per-recipient delivery status

All
POST
/api/campaigns/:id/activate

Activate a draft — queues all emails in SendGrid

Starter
POST
/api/campaigns/:id/pause

Pause an active campaign (stops further sends)

Starter
DELETE
/api/campaigns/:id

Delete a campaign

All

Account & stats

GET
/api/dashboard/stats

This month's usage: leads, enriched, campaigns, emails sent, LinkedIn sent

All
GET
/api/auth/me

Current user: plan, brand context, integration status, API key

All
POST
/api/auth/api-key

Generate or regenerate your API key. Returns new vj_ key.

All
DELETE
/api/auth/api-key

Revoke your API key immediately

All
GET
/api/brand

Read your brand context (name, offer, ICP, tone, goal)

All

Webhooks

VibeJoy can POST to your endpoint whenever a lead is found, enriched, or a campaign milestone is hit. Configure webhook URLs in Settings → Webhooks.

Available events

lead.found

A new intent-signal lead was discovered and saved

Starter
lead.enriched

Contact enrichment completed for a lead (email + title added)

Starter
campaign.created

A new campaign was generated (emails + LinkedIn post ready)

Starter
campaign.activated

A campaign was activated and emails are queued for sending

Starter
campaign.email_sent

An individual email was delivered to a recipient

Growth
campaign.reply_received

A reply to a campaign email was detected

Growth

Payload shape

{
  "event": "lead.enriched",
  "timestamp": "2026-07-08T09:00:00Z",
  "data": {
    "_id": "lead_abc123",
    "businessName": "Acme Corp",
    "contactName": "Jane Smith",
    "contactTitle": "Head of Sales",
    "email": "jane@acme.com",
    "linkedinUrl": "https://linkedin.com/in/janesmith",
    "website": "https://acme.com",
    "intentSource": "job_board",
    "intentSignal": "Hiring: Head of Sales at Acme Corp"
  }
}

Verifying signatures

Each request includes X-VibeJoy-Signature — an HMAC-SHA256 of the raw body using your webhook secret.

import crypto from 'crypto';

export function verifyWebhook(rawBody, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

// Express example
app.post('/webhook/vibejoy', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-vibejoy-signature'];
  if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const { event, data } = JSON.parse(req.body);
  console.log(event, data);
  res.sendStatus(200);
});

n8n & Make.com

Wire VibeJoy intent signals into your existing automation workflows using the HTTP Request node. No custom integration required.

Example n8n workflow

Schedule (Mon 8am)
VibeJoy: find hiring leads
VibeJoy: enrich contacts
VibeJoy: create campaign
Slack: notify team

n8n HTTP Request node — find leads

MethodPOST
URLhttp://localhost:3001/api/leads/intent/jobs
AuthenticationHeader Auth → Authorization: Bearer vj_...
Body (JSON){ "jobTitles": ["RevOps Manager"], "location": "London", "maxResults": 25 }

n8n HTTP Request node — create campaign

MethodPOST
URLhttp://localhost:3001/api/campaigns/generate
AuthenticationHeader Auth → Authorization: Bearer vj_...
Body (JSON){ "targetAudience": "RevOps managers", "campaignGoal": "book a demo" }

Make.com (Integromat)

Use the HTTP module in Make.com with the same base URL and Bearer token. Map leads[] from the find response into any downstream module.

# Make.com HTTP module: find leads
URL: http://localhost:3001/api/leads/intent/jobs
Method: POST
Headers: Authorization = Bearer vj_your_key
Body type: Raw (JSON)
Body: {"jobTitles": ["SDR"], "location": "London", "maxResults": 25}

# Map leads[].businessName, leads[].website, leads[]._id
# to your CRM, Airtable, or Notion module

Zapier (via webhook)

Use Zapier's Webhooks by Zapier action with a POST request to any VibeJoy endpoint. Chain: Zap trigger → POST to /api/leads/intent/jobs → loop results → POST to your CRM.

Questions or issues?

Raise issues or ask questions via the dashboard. Docs are updated continuously.

Get your API key →