DEV Community

Cover image for Build an Automated Creator Media Kit Generator
Olamide Olaniyan
Olamide Olaniyan

Posted on

Build an Automated Creator Media Kit Generator

"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:

  1. Profile overview – who they are, follower count, bio
  2. Audience demographics – where followers are, age/gender if available
  3. Engagement metrics – rate, avg likes, avg comments
  4. Top-performing content – their best posts with actual numbers
  5. Growth trajectory – are they growing or declining?
  6. 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;
}
Enter fullscreen mode Exit fullscreen mode

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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 };
}
Enter fullscreen mode Exit fullscreen mode

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),
  };
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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('');
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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%)
Enter fullscreen mode Exit fullscreen mode

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? 👇

webdev #api #nodejs #creatoreconomy #javascript

Top comments (0)