Recipes

Complete examples for the tasks integrations do most.

Each recipe is a working script. Names such as senderId, campaignId or crm stand for your own values and code. Every recipe assumes a client created with your key:

import Swarmhit from '@swarmhit/swarmhit-sdk';
const swarmhit = new Swarmhit(); // reads SWARMHIT_API_KEY

Launch a LinkedIn campaign

Pick the connected senders, create the campaign with its sequence and schedule, add leads, then start it.

const senders = [];
for await (const account of swarmhit.accounts.list()) {
  if (account.status === 'active') senders.push(account.id);
}

const campaign = await swarmhit.campaigns.create({
  name: 'Founders, Q4',
  linkedinAccountIds: senders,
  schedule: {
    timezone: 'Europe/Paris',
    activeHours: { from: '09:00', to: '18:00' },
    activeDays: [1, 2, 3, 4, 5], // Monday to Friday
  },
  sequence: {
    steps: [
      { type: 'visit_profile', nextDelayHours: 24 },
      {
        type: 'invite',
        config: {
          note: 'Hi {{firstName}}, I enjoyed your post about {{company}}.',
          fallbackNote: 'Hi, I enjoyed your recent post.', // for leads missing a variable
        },
        branchDelays: { no: 336 }, // give up after 14 days without an answer
      },
      { type: 'message', config: { text: 'Thanks for connecting, {{firstName}}!', fallbackText: 'Thanks for connecting!' } },
    ],
  },
});

await swarmhit.campaigns.leads.add(campaign.id, {
  leads: [
    { profileUrl: 'https://www.linkedin.com/in/ada-lovelace', firstName: 'Ada', company: 'Analytical Engines' },
    { profileUrl: 'https://www.linkedin.com/in/grace-hopper', firstName: 'Grace', company: 'Navy' },
  ],
});

await swarmhit.campaigns.update(campaign.id, { status: 'active' });

A campaign is created paused, so nothing is sent until you activate it. Activation checks the sequence: every message using a {{variable}} needs its fallback. The Campaigns guide explains every step type and setting.

Import a LinkedIn search into a campaign

Run a LinkedIn or Sales Navigator search through one of your senders, and enroll everyone it finds in a campaign:

const leadImport = await swarmhit.leadImports.create({
  accountId: senderId,
  url: 'https://www.linkedin.com/search/results/people/?keywords=founder%20saas',
  campaignId: campaign.id,
  maxLeads: 500,
  tags: ['saas-founders'],
});

// Imports run in the background. Poll it, or listen for the
// lead_import.completed webhook instead.
let current = leadImport;
while (current.status === 'pending' || current.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 30_000));
  current = await swarmhit.leadImports.get(leadImport.id);
}
console.log(`${current.status}: ${current.progress.importedLeads} leads imported`);

React to replies

When a lead replies, tag them, mark their interest and alert your team. This handler runs inside a webhook endpoint (see Receiving webhooks):

import type { WebhookEvent } from '@swarmhit/swarmhit-sdk';

async function onEvent(event: WebhookEvent) {
  if (event.event !== 'message.received' || !event.data.leadId) return;
  const { campaignId, leadId, message, from } = event.data;

  // tags replaces the lead's manual tags, so keep the ones it has.
  const lead = await swarmhit.leads.get(leadId);
  await swarmhit.leads.update(leadId, { tags: [...(lead.tags ?? []), 'replied'] });
  if (campaignId) {
    await swarmhit.campaigns.leads.setInterest(campaignId, leadId, { interest: 'interested' });
  }
  await postToSlack(`${from.firstName} ${from.lastName} replied: ${message.text}`);
}

A reply stops the lead's sequence on its own, so you don't need to pause it.

Sync campaign results to your CRM

Read every lead that replied, with its profile, and upsert it in your CRM. Run it on a schedule:

for await (const row of swarmhit.campaigns.leads.list(campaignId, { status: 'replied' })) {
  if (!row.leadId) continue;
  const lead = await swarmhit.leads.get(row.leadId);
  await crm.upsertContact({
    linkedin: lead.profileUrl,
    email: lead.email,
    name: `${lead.firstName} ${lead.lastName}`,
    company: lead.company,
    stage: row.interest === 'interested' ? 'opportunity' : 'contacted',
  });
}

Send a one-off invitation or message

Outside any campaign, you can act on one profile through one sender. The idempotency key makes the call safe to repeat: run the script twice within 24 hours and the person still gets one invitation.

await swarmhit.accounts.connections.send(
  senderId,
  { profileUrl: 'https://www.linkedin.com/in/ada-lovelace', note: 'Hi Ada, loved your talk!' },
  { idempotencyKey: 'invite-ada-2026-10' },
);

// Later, once connected:
await swarmhit.accounts.messages.send(senderId, {
  profileUrl: 'https://www.linkedin.com/in/ada-lovelace',
  text: 'Thanks for accepting, Ada!',
});

These actions count against the sender's daily limits, like campaign steps. See Direct actions.

Build a list from the B2B database

Find people by title and company size, save them to a list, and add the list to a campaign:

const list = await swarmhit.lists.create({ name: 'CTOs, 50-200 people' });

const leads = [];
for await (const person of swarmhit.database.people.search({
  filters: { titles: ['CTO'], headcountRanges: ['51-200'] },
  limit: 50,
})) {
  if (person.linkedinUrl) {
    leads.push({ profileUrl: person.linkedinUrl, firstName: person.firstName, lastName: person.lastName, company: person.company?.name ?? undefined });
  }
  if (leads.length === 200) break; // each result costs 2 credits
}

await swarmhit.lists.leads.add(list.id, { leads });
await swarmhit.campaigns.leads.add(campaign.id, { listId: list.id });

Check sender health every morning

for await (const account of swarmhit.accounts.list()) {
  if (account.status !== 'active') {
    await alert(`${account.fullName}: ${account.status} (${account.error ?? 'no detail'})`);
    continue;
  }
  const stats = await swarmhit.accounts.getStats(account.id);
  console.log(account.fullName, stats.limits);
}

Listen for the account.status_changed and account.paused webhooks to hear about problems as they happen instead.

On this page