"Can you send me your media kit?"
Every brand asks. Every creator panics.
They open Canva. Spend 3 hours making it look pretty. Manually type in their follower count (which changed since last week). Screenshot their best posts. Guess at their engagement rate. Send a PDF that's outdated the moment they export it.
What if you could generate a data-accurate, always-current media kit in 10 seconds? Pull live stats, calculate real engagement, highlight top-performing content, and output a structured report — all from API data.
Here's how to build it.
The Stack
- Node.js – runtime
- SociaVault API – profile, post, and engagement data
- PDFKit – PDF generation (or JSON for API consumers)
What Goes in a Media Kit
Every brand wants the same 6 things:
- Profile overview – who they are, follower count, bio
- Audience demographics – where followers are, age/gender if available
- Engagement metrics – rate, avg likes, avg comments
- Top-performing content – their best posts with actual numbers
- Growth trajectory – are they growing or declining?
- Platform presence – which platforms, how big on each
Let's pull all of this from real data.
Step 1: Fetch All the Data
const axios = require('axios');
const API_BASE = 'https://api.sociavault.com/v1';
const API_KEY = process.env.SOCIAVAULT_API_KEY;
const api = axios.create({
baseURL: API_BASE,
headers: { 'x-api-key': API_KEY },
});
async function fetchCreatorData(platform, username) {
// Fetch profile
const { data: profile } = await api.get(`/${platform}/profile/${username}`);
// Fetch recent posts
const { data: postData } = await api.get(`/${platform}/posts/${username}`, {
params: { limit: 50 },
});
const posts = postData.posts;
return { profile, posts };
}
// Fetch across multiple platforms
async function fetchMultiPlatform(platforms) {
const results = {};
for (const { platform, username } of platforms) {
try {
results[platform] = await fetchCreatorData(platform, username);
} catch (err) {
console.error(`Failed to fetch ${platform}/@${username}: ${err.message}`);
}
}
return results;
}
Step 2: Calculate Engagement Metrics
Brands care about engagement rate more than follower count. Calculate it properly.
function calculateEngagementMetrics(profile, posts) {
const followers = profile.followerCount;
const metrics = posts.map(post => {
const likes = post.likeCount || 0;
const comments = post.commentCount || 0;
const shares = post.shareCount || 0;
const views = post.viewCount || 0;
return {
id: post.id,
likes,
comments,
shares,
views,
engagementRate: followers > 0 ? ((likes + comments) / followers) * 100 : 0,
caption: (post.caption || '').slice(0, 100),
createdAt: post.createdAt,
mediaUrl: post.thumbnailUrl || post.mediaUrl,
};
});
const avgEngagement = metrics.reduce((s, m) => s + m.engagementRate, 0) / metrics.length;
const avgLikes = Math.round(metrics.reduce((s, m) => s + m.likes, 0) / metrics.length);
const avgComments = Math.round(metrics.reduce((s, m) => s + m.comments, 0) / metrics.length);
const avgViews = Math.round(metrics.reduce((s, m) => s + m.views, 0) / metrics.length);
// Posting frequency
const dates = posts.map(p => new Date(p.createdAt)).sort((a, b) => b - a);
const daySpan = (dates[0] - dates[dates.length - 1]) / (1000 * 60 * 60 * 24);
const postsPerWeek = daySpan > 0 ? parseFloat((posts.length / daySpan * 7).toFixed(1)) : 0;
return {
avgEngagementRate: parseFloat(avgEngagement.toFixed(2)),
avgLikes,
avgComments,
avgViews,
postsPerWeek,
totalPosts: profile.postCount,
postMetrics: metrics,
};
}
Step 3: Identify Top Content
Brands want to see what works. Surface the creator's best performing posts.
function getTopContent(postMetrics, count = 5) {
// Top by engagement rate
const topEngagement = [...postMetrics]
.sort((a, b) => b.engagementRate - a.engagementRate)
.slice(0, count);
// Top by views (for video platforms)
const topViews = [...postMetrics]
.sort((a, b) => b.views - a.views)
.slice(0, count);
// Most commented (indicates conversation)
const topComments = [...postMetrics]
.sort((a, b) => b.comments - a.comments)
.slice(0, count);
return { topEngagement, topViews, topComments };
}
Step 4: Analyze Growth Trajectory
Is the creator growing, flat, or declining? Compare recent performance to older performance.
function analyzeGrowth(posts) {
if (posts.length < 10) return { trend: 'insufficient data', confidence: 'low' };
const sorted = [...posts].sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
const midpoint = Math.floor(sorted.length / 2);
const olderHalf = sorted.slice(0, midpoint);
const newerHalf = sorted.slice(midpoint);
const avgOlder = olderHalf.reduce((s, p) => s + (p.likeCount || 0), 0) / olderHalf.length;
const avgNewer = newerHalf.reduce((s, p) => s + (p.likeCount || 0), 0) / newerHalf.length;
const changePercent = ((avgNewer - avgOlder) / avgOlder) * 100;
let trend, emoji;
if (changePercent > 20) { trend = 'Strong Growth'; emoji = '📈'; }
else if (changePercent > 5) { trend = 'Growing'; emoji = '↗️'; }
else if (changePercent > -5) { trend = 'Stable'; emoji = '➡️'; }
else if (changePercent > -20) { trend = 'Declining'; emoji = '↘️'; }
else { trend = 'Sharp Decline'; emoji = '📉'; }
return {
trend,
emoji,
changePercent: parseFloat(changePercent.toFixed(1)),
olderAvgLikes: Math.round(avgOlder),
newerAvgLikes: Math.round(avgNewer),
};
}
Step 5: Generate the Media Kit
function generateMediaKit(platformData) {
const kit = {
generatedAt: new Date().toISOString(),
platforms: {},
summary: {},
};
let totalFollowers = 0;
let totalEngagementSum = 0;
let platformCount = 0;
for (const [platform, { profile, posts }] of Object.entries(platformData)) {
const metrics = calculateEngagementMetrics(profile, posts);
const topContent = getTopContent(metrics.postMetrics);
const growth = analyzeGrowth(posts);
kit.platforms[platform] = {
username: profile.username,
displayName: profile.fullName || profile.displayName,
bio: profile.biography,
followers: profile.followerCount,
following: profile.followingCount,
profilePicUrl: profile.profilePicUrl,
verified: profile.isVerified || false,
metrics: {
engagementRate: metrics.avgEngagementRate,
avgLikes: metrics.avgLikes,
avgComments: metrics.avgComments,
avgViews: metrics.avgViews,
postsPerWeek: metrics.postsPerWeek,
},
growth,
topContent: topContent.topEngagement.map(p => ({
caption: p.caption,
likes: p.likes,
comments: p.comments,
views: p.views,
engagementRate: parseFloat(p.engagementRate.toFixed(2)),
date: p.createdAt,
})),
};
totalFollowers += profile.followerCount;
totalEngagementSum += metrics.avgEngagementRate;
platformCount++;
}
// Cross-platform summary
kit.summary = {
totalFollowers,
platformCount,
avgEngagementRate: parseFloat((totalEngagementSum / platformCount).toFixed(2)),
platforms: Object.keys(kit.platforms),
};
return kit;
}
Step 6: Pretty-Print the Report
function printMediaKit(kit) {
console.log('\n╔═══════════════════════════════════════╗');
console.log('║ CREATOR MEDIA KIT ║');
console.log(`║ Generated: ${new Date(kit.generatedAt).toLocaleDateString()} ║`);
console.log('╚═══════════════════════════════════════╝\n');
console.log(`🌐 Total Reach: ${kit.summary.totalFollowers.toLocaleString()} followers across ${kit.summary.platformCount} platforms`);
console.log(`📊 Avg Engagement: ${kit.summary.avgEngagementRate}%\n`);
for (const [platform, data] of Object.entries(kit.platforms)) {
console.log(`━━━ ${platform.toUpperCase()} ━━━`);
console.log(` @${data.username} ${data.verified ? '✓' : ''}`);
console.log(` ${data.followers.toLocaleString()} followers`);
console.log(` Engagement: ${data.metrics.engagementRate}% | ${data.metrics.avgLikes.toLocaleString()} avg likes | ${data.metrics.avgComments.toLocaleString()} avg comments`);
console.log(` Posting: ${data.metrics.postsPerWeek}x/week`);
console.log(` Trend: ${data.growth.emoji} ${data.growth.trend} (${data.growth.changePercent > 0 ? '+' : ''}${data.growth.changePercent}%)`);
console.log(` Top Content:`);
data.topContent.slice(0, 3).forEach((post, i) => {
console.log(` ${i + 1}. "${post.caption}..." — ${post.likes.toLocaleString()} likes, ${post.engagementRate}% ER`);
});
console.log('');
}
}
Running It
async function createMediaKit() {
const platformData = await fetchMultiPlatform([
{ platform: 'tiktok', username: 'creator_handle' },
{ platform: 'instagram', username: 'creator_handle' },
{ platform: 'youtube', username: 'creator_handle' },
]);
const kit = generateMediaKit(platformData);
printMediaKit(kit);
return kit; // Return JSON for API use or PDF generation
}
createMediaKit();
Sample Output
╔═══════════════════════════════════════╗
║ CREATOR MEDIA KIT ║
║ Generated: 3/10/2026 ║
╚═══════════════════════════════════════╝
🌐 Total Reach: 312,400 followers across 3 platforms
📊 Avg Engagement: 4.2%
━━━ TIKTOK ━━━
@sarah_creates ✓
148,200 followers
Engagement: 5.8% | 8,595 avg likes | 342 avg comments
Posting: 4.2x/week
Trend: 📈 Strong Growth (+34.2%)
Top Content:
1. "3 hacks brands don't want you to know..." — 45,200 likes, 12.3% ER
2. "Replying to @user how I actually edit..." — 31,800 likes, 9.1% ER
3. "POV: your first brand deal..." — 28,400 likes, 8.7% ER
━━━ INSTAGRAM ━━━
@sarah_creates ✓
97,800 followers
Engagement: 3.2% | 3,129 avg likes | 187 avg comments
Posting: 3.1x/week
Trend: ↗️ Growing (+11.8%)
━━━ YOUTUBE ━━━
@SarahCreates
66,400 followers
Engagement: 3.6% | 2,390 avg likes | 298 avg comments
Posting: 1.0x/week
Trend: 📈 Strong Growth (+28.5%)
Every number is real. Every metric is current. No manual data entry. No stale screenshots.
Read the Full Guide
This is a condensed version. The full guide includes:
- PDF generation with PDFKit (branded, client-ready)
- Audience demographics integration
- Estimated CPM/CPE calculations
- White-label media kit API endpoint
Read the complete guide on SociaVault →
Building creator tools? SociaVault provides social media data APIs for TikTok, Instagram, YouTube, and 10+ platforms. Fetch profiles, posts, engagement, and audience data through one unified API.
Discussion
Are you a creator who dreads making media kits? Or a developer building tools for creators? What would you add to this? 👇
Top comments (0)