Build a Social Media CRM in Notion Using API Data
Notion is already where your team lives. Instead of switching to another tool for influencer tracking or competitor monitoring, you can turn Notion into a social media CRM — automatically populated with real data from SociaVault's API.
By the end of this guide, you'll have a Notion database that:
- Auto-fills profile stats for any creator you add
- Tracks follower growth over time
- Flags accounts that meet your campaign criteria
- Updates weekly without manual work
No expensive influencer marketing platforms. Just Notion + a script that runs on schedule.
What You'll Build
A Notion database that looks like this:
| Creator | Platform | Followers | Eng. Rate | Bio | Verified | Status | Last Updated |
|---|---|---|---|---|---|---|---|
| @natgeo | 284M | 0.12% | Inspiring... | ✅ | Contacted | 2026-04-08 | |
| @charlidamelio | TikTok | 155M | 4.2% | ❤️ | ✅ | In Review | 2026-04-08 |
| @mkbhd | YouTube | 19.8M | — | — | ✅ | Shortlisted | 2026-04-08 |
The data columns fill automatically. You only manage the workflow columns (Status, Notes, Campaign).
Step 1: Create the Notion Database
Create a new full-page database in Notion with these properties:
| Property | Type | Purpose |
|---|---|---|
| Creator | Title | Username/handle |
| Platform | Select | Instagram, TikTok, YouTube, Twitter |
| Followers | Number | Auto-filled |
| Following | Number | Auto-filled |
| Posts Count | Number | Auto-filled |
| Engagement Rate | Number (%) | Calculated |
| Biography | Text | Auto-filled |
| Is Verified | Checkbox | Auto-filled |
| Profile URL | URL | Auto-filled |
| Status | Select | Prospect, Contacted, Negotiating, Active, Declined |
| Campaign | Multi-select | Your campaign tags |
| Notes | Text | Manual notes |
| Last Updated | Date | Auto-filled timestamp |
Step 2: Set Up the Notion API
- Go to developers.notion.com
- Create a new integration — name it "Social Media CRM"
- Copy the Internal Integration Token
- Share your database with the integration (click ••• → Connections → your integration)
- Copy the database ID from the URL:
notion.so/{database_id}?v=...
Step 3: The Sync Script
This Node.js script reads creators from your Notion database and enriches them with SociaVault data:
const NOTION_KEY = process.env.NOTION_KEY;
const NOTION_DB = process.env.NOTION_DB_ID;
const SOCIAVAULT_KEY = process.env.SOCIAVAULT_API_KEY;
// Get all creators from Notion database
async function getNotionCreators() {
const res = await fetch(`https://api.notion.com/v1/databases/${NOTION_DB}/query`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${NOTION_KEY}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json'
},
body: JSON.stringify({
filter: {
property: 'Platform',
select: { is_not_empty: true }
}
})
});
return (await res.json()).results;
}
// Get social media data from SociaVault
async function getProfileData(handle, platform) {
const platformEndpoints = {
'Instagram': 'instagram/profile',
'TikTok': 'tiktok/profile',
'YouTube': 'youtube/channel',
'Twitter': 'twitter/profile'
};
const endpoint = platformEndpoints[platform];
if (!endpoint) return null;
const param = platform === 'YouTube' ? 'handle' : 'handle';
const url = `https://api.sociavault.com/v1/scrape/${endpoint}?${param}=${encodeURIComponent(handle)}`;
const res = await fetch(url, {
headers: { 'X-API-Key': SOCIAVAULT_KEY }
});
return (await res.json()).data;
}
// Normalize data across platforms
function normalizeProfile(data, platform) {
switch (platform) {
case 'Instagram':
return {
followers: data.follower_count,
following: data.following_count,
posts: data.media_count,
bio: data.biography,
verified: data.is_verified,
url: `https://instagram.com/${data.username}`
};
case 'TikTok':
return {
followers: data.stats?.followerCount,
following: data.stats?.followingCount,
posts: data.stats?.videoCount,
bio: data.user?.signature,
verified: data.user?.verified,
url: `https://tiktok.com/@${data.user?.uniqueId}`
};
case 'YouTube':
return {
followers: parseInt(data.subscriberCount) || 0,
following: 0,
posts: parseInt(data.videoCount) || 0,
bio: data.description?.substring(0, 200),
verified: false,
url: `https://youtube.com/@${data.customUrl || data.handle}`
};
case 'Twitter':
return {
followers: data.legacy?.followers_count,
following: data.legacy?.friends_count,
posts: data.legacy?.statuses_count,
bio: data.legacy?.description,
verified: data.is_blue_verified,
url: `https://x.com/${data.core?.screen_name || data.legacy?.screen_name}`
};
default:
return null;
}
}
// Update Notion page with profile data
async function updateNotionPage(pageId, profile) {
await fetch(`https://api.notion.com/v1/pages/${pageId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${NOTION_KEY}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json'
},
body: JSON.stringify({
properties: {
'Followers': { number: profile.followers },
'Following': { number: profile.following },
'Posts Count': { number: profile.posts },
'Biography': { rich_text: [{ text: { content: (profile.bio || '').substring(0, 2000) } }] },
'Is Verified': { checkbox: profile.verified || false },
'Profile URL': { url: profile.url },
'Last Updated': { date: { start: new Date().toISOString().split('T')[0] } }
}
})
});
}
// Main sync function
async function syncCRM() {
const creators = await getNotionCreators();
console.log(`Found ${creators.length} creators to sync`);
for (const page of creators) {
const handle = page.properties.Creator?.title?.[0]?.plain_text;
const platform = page.properties.Platform?.select?.name;
if (!handle || !platform) continue;
try {
const rawData = await getProfileData(handle, platform);
if (!rawData) continue;
const profile = normalizeProfile(rawData, platform);
if (!profile) continue;
await updateNotionPage(page.id, profile);
console.log(`✓ Updated ${handle} (${platform})`);
// Rate limiting
await new Promise(r => setTimeout(r, 1000));
} catch (err) {
console.error(`✗ Failed ${handle}: ${err.message}`);
}
}
console.log('Sync complete');
}
syncCRM();
The Python version:
import os
import time
import requests
from datetime import date
NOTION_KEY = os.environ["NOTION_KEY"]
NOTION_DB = os.environ["NOTION_DB_ID"]
API_KEY = os.environ["SOCIAVAULT_API_KEY"]
BASE = "https://api.sociavault.com/v1/scrape"
HEADERS = {"X-API-Key": API_KEY}
PLATFORM_ENDPOINTS = {
"Instagram": "instagram/profile",
"TikTok": "tiktok/profile",
"YouTube": "youtube/channel",
"Twitter": "twitter/profile",
}
def get_notion_creators():
url = f"https://api.notion.com/v1/databases/{NOTION_DB}/query"
headers = {
"Authorization": f"Bearer {NOTION_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json",
}
body = {"filter": {"property": "Platform", "select": {"is_not_empty": True}}}
return requests.post(url, headers=headers, json=body).json()["results"]
def get_profile(handle, platform):
endpoint = PLATFORM_ENDPOINTS.get(platform)
if not endpoint:
return None
r = requests.get(f"{BASE}/{endpoint}", headers=HEADERS, params={"handle": handle})
return r.json().get("data")
def normalize(data, platform):
if platform == "Instagram":
return {
"followers": data.get("follower_count", 0),
"following": data.get("following_count", 0),
"posts": data.get("media_count", 0),
"bio": data.get("biography", ""),
"verified": data.get("is_verified", False),
"url": f"https://instagram.com/{data.get('username', '')}",
}
elif platform == "TikTok":
stats = data.get("stats", {})
user = data.get("user", {})
return {
"followers": stats.get("followerCount", 0),
"following": stats.get("followingCount", 0),
"posts": stats.get("videoCount", 0),
"bio": user.get("signature", ""),
"verified": user.get("verified", False),
"url": f"https://tiktok.com/@{user.get('uniqueId', '')}",
}
elif platform == "Twitter":
legacy = data.get("legacy", {})
return {
"followers": legacy.get("followers_count", 0),
"following": legacy.get("friends_count", 0),
"posts": legacy.get("statuses_count", 0),
"bio": legacy.get("description", ""),
"verified": data.get("is_blue_verified", False),
"url": f"https://x.com/{legacy.get('screen_name', '')}",
}
return None
def update_notion(page_id, profile):
url = f"https://api.notion.com/v1/pages/{page_id}"
headers = {
"Authorization": f"Bearer {NOTION_KEY}",
"Notion-Version": "2022-06-28",
"Content-Type": "application/json",
}
body = {
"properties": {
"Followers": {"number": profile["followers"]},
"Following": {"number": profile["following"]},
"Posts Count": {"number": profile["posts"]},
"Biography": {"rich_text": [{"text": {"content": profile["bio"][:2000]}}]},
"Is Verified": {"checkbox": profile["verified"]},
"Profile URL": {"url": profile["url"]},
"Last Updated": {"date": {"start": str(date.today())}},
}
}
requests.patch(url, headers=headers, json=body)
def main():
creators = get_notion_creators()
print(f"Syncing {len(creators)} creators...")
for page in creators:
props = page["properties"]
title_arr = props.get("Creator", {}).get("title", [])
handle = title_arr[0]["plain_text"] if title_arr else None
platform = props.get("Platform", {}).get("select", {}).get("name")
if not handle or not platform:
continue
try:
raw = get_profile(handle, platform)
if not raw:
continue
profile = normalize(raw, platform)
if not profile:
continue
update_notion(page["id"], profile)
print(f" ✓ {handle} ({platform})")
time.sleep(1)
except Exception as e:
print(f" ✗ {handle}: {e}")
print("Done.")
main()
Step 4: Automate the Sync
Option A: Cron Job (VPS/Server)
# Run every day at 8 AM
0 8 * * * cd /path/to/crm-sync && node sync.js >> sync.log 2>&1
Option B: GitHub Actions (Free)
Create .github/workflows/crm-sync.yml:
name: Sync CRM
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: node sync.js
env:
NOTION_KEY: ${{ secrets.NOTION_KEY }}
NOTION_DB_ID: ${{ secrets.NOTION_DB_ID }}
SOCIAVAULT_API_KEY: ${{ secrets.SOCIAVAULT_API_KEY }}
Option C: Make.com
Use the Make.com integration guide to build a visual workflow that syncs Notion automatically.
Adding Engagement Rate Calculation
You can calculate engagement rates by also pulling recent posts:
async function getEngagementRate(handle, platform) {
if (platform !== 'Instagram') return null;
// Get profile for follower count
const profileRes = await fetch(
`https://api.sociavault.com/v1/scrape/instagram/profile?handle=${handle}`,
{ headers: { 'X-API-Key': SOCIAVAULT_KEY } }
);
const profile = (await profileRes.json()).data;
// Get recent posts
const postsRes = await fetch(
`https://api.sociavault.com/v1/scrape/instagram/posts?handle=${handle}`,
{ headers: { 'X-API-Key': SOCIAVAULT_KEY } }
);
const posts = (await postsRes.json()).data;
if (!posts?.length || !profile?.follower_count) return null;
const totalEngagement = posts.reduce((sum, post) => {
return sum + (post.like_count || 0) + (post.comment_count || 0);
}, 0);
return (totalEngagement / posts.length / profile.follower_count) * 100;
}
Add this to your sync loop and update the Notion Engagement Rate property.
Notion Views for Your CRM
Once data is flowing, create these filtered views:
"Hot Prospects" View
- Filter: Followers > 10,000 AND Status = "Prospect"
- Sort: Followers descending
"Campaign Ready" View
- Filter: Status = "Shortlisted" AND Is Verified = true
- Group by: Campaign
"Weekly Growth" View
- Filter: Last Updated = this week
- Sort: Last Updated descending
"Platform Mix" View
- Group by: Platform
- Show: Count, Average followers
SociaVault CRM vs. Paid Platforms
| Feature | SociaVault + Notion | Grin | CreatorIQ |
|---|---|---|---|
| Monthly Cost | ~$10-30 | $2,500+ | Custom ($$$) |
| Profiles tracked | Unlimited | Plan-dependent | Plan-dependent |
| Custom fields | ✅ Unlimited | Limited | Limited |
| Workflow integration | ✅ Notion native | Separate tool | Separate tool |
| Multi-platform | ✅ 10+ platforms | Instagram focus | Multi |
| Ownership | ✅ Your data | Vendor lock-in | Vendor lock-in |
For most agencies and small brands, SociaVault + Notion beats $2,500/month platforms — especially when you only need data tracking, not full campaign management.
Get Started
Sign up free and start building your Notion CRM today.
Related Reading
Found this helpful?
Share it with others who might benefit
Ready to Try SociaVault?
Start extracting social media data with our powerful API. No credit card required.