DEV Community

Cover image for How We Built a Live Media Kit Generator That Pulls Real Instagram Data in Under 30 Seconds
Kollab Kit
Kollab Kit

Posted on

How We Built a Live Media Kit Generator That Pulls Real Instagram Data in Under 30 Seconds

Most creator tools are just fancy form builders with a PDF export button.

You fill in your follower count manually. Upload a screenshot of your analytics. Pick a Canva template. Export a PDF. Send it to a brand. Three weeks later your numbers have changed and the PDF is already wrong.

We wanted to build something different with KollabKit — a live, API-connected media kit that generates in under 30 seconds. Here's how we built it.

Table of Contents

The Problem
Step 1: Instagram Graph API
Step 2: The OAuth Flow
Step 3: Pulling the Metrics
Step 4: Computing Engagement Rate
Step 5: The Live URL Architecture
Step 6: Rate Card Calculation
What We Learned
What's Next


The Problem

A media kit is basically a snapshot of a creator's performance at a point in time. The problem with PDFs is that the snapshot goes stale the moment you export it.

What we needed wasn't a document generator. It was a live data layer sitting between Instagram's API and a shareable public URL, refreshing whenever someone opens the link.

The architecture had to handle three things:

  1. OAuth + API connection - connect to Instagram's official Graph API without storing credentials insecurely
  2. Data normalisation - take raw API responses and turn them into readable metrics like engagement rate, audience split, and top cities
  3. Live rendering - serve a public URL that always shows current data, not a cached snapshot

Step 1: Instagram Graph API

This is where most builders run into trouble. Instagram's API has two tiers:

Basic Display API - deprecated as of late 2024. Don't build on this.

Instagram Graph API (via Meta for Developers) - the current option. Requires the creator to have a Professional account (Creator or Business) connected to a Facebook Page.

The permissions you need for a media kit:

instagram_basic
instagram_manage_insights
pages_show_list
pages_read_engagement
Enter fullscreen mode Exit fullscreen mode

instagram_manage_insights is the important one. It gives you account-level metrics like impressions, reach, follower demographics, and top locations.

Getting these approved by Meta means submitting your app for review with a screen recording showing exactly how you use each permission. Set aside 2 to 4 weeks for this. It's annoying but there's no way around it if you want verified data.


Step 2: The OAuth Flow

We went with a standard OAuth 2.0 flow:

// Step 1: Redirect user to Meta's OAuth dialog
const authUrl = `https://www.facebook.com/v19.0/dialog/oauth?
  client_id=${process.env.META_APP_ID}
  &redirect_uri=${encodeURIComponent(process.env.REDIRECT_URI)}
  &scope=instagram_basic,instagram_manage_insights,pages_show_list
  &response_type=code`;

// Step 2: Exchange code for short-lived token
const tokenResponse = await fetch(
  `https://graph.facebook.com/v19.0/oauth/access_token?
    client_id=${process.env.META_APP_ID}
    &client_secret=${process.env.META_APP_SECRET}
    &redirect_uri=${encodeURIComponent(process.env.REDIRECT_URI)}
    &code=${authCode}`
);

// Step 3: Exchange for long-lived token (60-day expiry)
const longLivedToken = await fetch(
  `https://graph.facebook.com/v19.0/oauth/access_token?
    grant_type=fb_exchange_token
    &client_id=${process.env.META_APP_ID}
    &client_secret=${process.env.META_APP_SECRET}
    &fb_exchange_token=${shortLivedToken}`
);
Enter fullscreen mode Exit fullscreen mode

Long-lived tokens expire after 60 days. We handle the refresh server-side before expiry so the creator never has to reconnect unless they revoke access themselves.


Step 3: Pulling the Metrics

Once you have a valid token, pulling the data is fairly simple. The tricky bit is knowing which endpoints to chain together.

// Get connected Instagram Business Account ID
const igAccountRes = await fetch(
  `https://graph.facebook.com/v19.0/me/accounts?
    fields=instagram_business_account&access_token=${token}`
);
const igAccountId = igAccountRes.data[0].instagram_business_account.id;

// Pull core profile metrics
const profileRes = await fetch(
  `https://graph.facebook.com/v19.0/${igAccountId}?
    fields=username,name,biography,followers_count,
    media_count,profile_picture_url,website&
    access_token=${token}`
);

// Pull insights - engagement, reach, impressions
const insightsRes = await fetch(
  `https://graph.facebook.com/v19.0/${igAccountId}/insights?
    metric=impressions,reach,follower_count&
    period=day&
    since=${thirtyDaysAgo}&
    until=${today}&
    access_token=${token}`
);

// Pull audience demographics
const audienceRes = await fetch(
  `https://graph.facebook.com/v19.0/${igAccountId}/insights?
    metric=audience_city,audience_country,
    audience_gender_age&
    period=lifetime&
    access_token=${token}`
);
Enter fullscreen mode Exit fullscreen mode

Gotcha: audience_city, audience_country, and audience_gender_age only work with period=lifetime. They don't support date ranges. You get the current distribution as of whenever you make the call.


Step 4: Computing Engagement Rate

Instagram's API doesn't give you engagement rate directly. You have to compute it from the raw data:

// Pull recent media for engagement calculation
const mediaRes = await fetch(
  `https://graph.facebook.com/v19.0/${igAccountId}/media?
    fields=like_count,comments_count,timestamp&
    limit=30&access_token=${token}`
);

const posts = mediaRes.data;
const totalEngagements = posts.reduce((sum, post) => {
  return sum + (post.like_count || 0) + (post.comments_count || 0);
}, 0);

const avgEngagementsPerPost = totalEngagements / posts.length;
const engagementRate = (avgEngagementsPerPost / followersCount) * 100;

// Round to 2 decimal places
const displayEngagementRate = Math.round(engagementRate * 100) / 100;
Enter fullscreen mode Exit fullscreen mode

We sample the last 30 posts. Fewer posts makes the rate jump around too much. More posts pulls in older content that doesn't reflect where the account is today.


Step 5: The Live URL Architecture

We didn't want to cache the media kit as a static page. We wanted it to show current data every time a brand opens the link. But we also couldn't hit Instagram's API fresh on every page load — rate limits and latency would kill that fast.

So we went with a short-TTL cache layer:

async function getMediaKitData(creatorId) {
  const cacheKey = `mediakit:${creatorId}`;

  // Check cache first (TTL: 6 hours)
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // Cache miss - pull fresh from Instagram API
  const freshData = await pullInstagramData(creatorId);

  // Store with 6-hour expiry
  await redis.setex(cacheKey, 21600, JSON.stringify(freshData));

  return freshData;
}
Enter fullscreen mode Exit fullscreen mode

6 hours works well here. Creator metrics don't shift meaningfully in that window, but brands always see data that's at most half a day old rather than a 3-month-old PDF. When a creator connects or manually refreshes, we invalidate the cache immediately.


Step 6: Rate Card Calculation

The rate calculator is its own module. We don't share the exact formula publicly, but the general logic follows industry benchmarks:

Factor How it works
Follower tier Base rate for nano / micro / mid / macro / mega
Engagement rate Higher engagement = higher multiplier
Niche coefficient Finance and tech creators charge more than lifestyle
Content format Reel, Story, Static Post, YouTube — each has its own rate

We started the ranges from aggregated industry data and keep refining them as more real deal data flows through the platform.


What We Learned

Meta's API review is the actual bottleneck. The engineering wasn't the hard part. Waiting for permission approval and writing data usage policy documentation took longer than building the data pipeline.

Follower count is a bad primary metric. Once you work with real engagement data you see how misleading follower counts are. A creator with 20K followers and 8% engagement is worth more to a brand than one with 200K and 0.4%. We built the kit to lead with engagement, not followers.

Token refresh needs to be invisible. If creators have to reconnect their Instagram every 60 days, churn goes up. Silent background refresh isn't a nice-to-have.

URL format matters more than you'd think. We tried slugs, hashes, numeric IDs. Short readable custom slugs like kollabkit.com/kit/username got significantly better click-through from brand email chains than opaque hash URLs.


What's Next

  • YouTube Data API - same OAuth flow, different endpoints, adding video metrics alongside Instagram
  • AI caption suggestor - trained on niche-specific content to help creators write brand-aligned captions faster
  • AI video deliverable approver - brands submit a brief, creators submit a video, our pipeline checks compliance before it hits a human reviewer
  • Creator discovery - search across verified creator profiles using real engagement data, not self-reported numbers

If you're building in the creator economy and want to talk through the Instagram Graph API — especially the permission review process — drop a comment. Happy to share what worked and what didn't.

And if you're a creator or work with them, the media kit is free to try.


Tags: javascript webdev api startup instagram buildinpublic saas productivity

Top comments (0)