# Pull The Swarm people and relationship data on Airtable

## Relationship intelligence on Airtable

[Airtable](https://clay.com/) is the go-to for flexible databases and workflows, and now you can enrich it with Swarm relationship intelligence data. Map intro paths to your target companies and buyers based on your team and stakeholders' work history, education overlaps, social networks, and investor networks.

Whether you're building a custom CRM, prospect, partner, or customer records on Airtable, The Swarm will help you surface warm intro paths, without leaving your base!

"We've been using The Swarm API to pull relationship data from The Swarm on Airtable as part of one of our core workflows."

Andy Mowat

Founder at Whispered, former VP of RevOps at Carta

# How to get started?

1. Watch the 2-min demo [here](https://www.loom.com/share/f920928596174483ba0a4b865a9e4c4e).
2. Start an account at [app.theswarm.com](https://app.theswarm.com/) (14-day trial, plans start at $99/month) and retrieve your API key [here](https://app.theswarm.com/team-settings/api).
3. Create an Airtable automation (Team plan required) with the script below and replace YOURKEY).

```javascript
/**
 * Swarm — Network Mapper → Airtable (per official docs)
 * Table fields:
 *  - "Domain" (text)
 *  - "Relationship Count" (number)
 *  - "Top Connector" (text)
 *  - "Paths (JSON)" (long text)
 */

const TABLE_NAME = 'Profiles';
const DOMAIN_FIELD = 'Domain';
const REL_COUNT_FIELD = 'Relationship Count';
const TOP_CONNECTOR_FIELD = 'Top Connector';
const PATHS_JSON_FIELD = 'Paths (JSON)';

const SWARM_API_KEY = 'YOURKEY';
const SWARM_URL = 'https://bee.theswarm.com/v2/profiles/network-mapper';

// ---------- helpers ----------
function chunk(arr, n) { const out=[]; for (let i=0;i<arr.length;i+=n) out.push(arr.slice(i,i+n)); return out; }
function toItems(data) { return (data && Array.isArray(data.items)) ? data.items : []; }
function normDomain(raw) {
  if (!raw) return '';
  let d = String(raw).trim().toLowerCase();
  d = d.replace(/^https?:\/\//,'').replace(/^www\./,'').split('/')[0];
  return d;
}

// ---------- main ----------
const table = base.getTable(TABLE_NAME);
const query = await table.selectRecordsAsync();

const updates = [];
let processed = 0;

for (const record of query.records) {
  const domainRaw = record.getCellValue(DOMAIN_FIELD);
  if (!domainRaw) continue;
  const domain = normDomain(domainRaw);

// Per docs: term on profile_info.current_company_website with { value: "<domain>" }
  const body = {
    query: {
      term: {
        'profile_info.current_company_website': { value: domain }
      }
    }
  };

let json;
  try {
    const resp = await fetch(SWARM_URL, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'x-api-key': SWARM_API_KEY },
      body: JSON.stringify(body)
    });
    if (!resp.ok) {
      const txt = await resp.text();
      console.log(`API failed for ${domain}: ${resp.status} ${resp.statusText} :: ${txt.slice(0,300)}`);
      // write zeros so the row is “done”
      updates.push({ id: record.id, fields: {
        [REL_COUNT_FIELD]: 0, [TOP_CONNECTOR_FIELD]: '', [PATHS_JSON_FIELD]: '[]'
      }});
      processed += 1;
      continue;
    }
    json = await resp.json();
  } catch (e) {
    console.log(`Request error for ${domain}: ${e.message}`);
    continue;
  }

const items = toItems(json);

// Flatten connections
  const connections = [];
  for (const item of items) {
    const profile = item?.profile || {};
    const conns = Array.isArray(item?.connections) ? item.connections : [];
    for (const c of conns) {
      connections.push({
        profileName: profile.full_name || '',
        profileTitle: profile.current_title || '',
        profileCompany: profile.current_company_name || '',
        profileWebsite: profile.current_company_website || '',
        teamUser: c.connector_name || c.connector_id || '',
        teamUserLinkedIn: c.connector_linkedin_url || '',
        strength: typeof c.connection_strength === 'number'
          ? c.connection_strength
          : (typeof c.connection_strength_normalized === 'number' ? c.connection_strength_normalized : 0),
        sources: (c.sources || []).map(s => s?.origin).filter(Boolean)
      });
    }
  }

const relationshipCount = connections.length;
  let topConnector = '';
  if (relationshipCount > 0) {
    connections.sort((a,b)=>(b.strength||0)-(a.strength||0));
    const t = connections[0];
    topConnector = `${t.teamUser} -> ${t.profileName} (${(t.strength||0).toFixed(2)})`;
  }

// Compact JSON for Airtable cell limits
  const compact = connections.map(c => ({
    profileName: c.profileName,
    profileTitle: c.profileTitle,
    teamUser: c.teamUser,
    strength: c.strength,
    sources: [...new Set(c.sources)]
  }));

updates.push({
    id: record.id,
    fields: {
      [REL_COUNT_FIELD]: relationshipCount,
      [TOP_CONNECTOR_FIELD]: topConnector,
      [PATHS_JSON_FIELD]: JSON.stringify(compact, null, 2)
    }
  });

processed += 1;
}

// Batch updates
for (const group of chunk(updates, 50)) {
  await table.updateRecordsAsync(group);
}

console.log(`Processed ${processed} record(s). Updated ${updates.length} record(s).`);
```

## Switch to real-time people and relationship data.
