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:
- OAuth + API connection - connect to Instagram's official Graph API without storing credentials insecurely
- Data normalisation - take raw API responses and turn them into readable metrics like engagement rate, audience split, and top cities
- 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
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}`
);
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}`
);
Gotcha:
audience_city,audience_country, andaudience_gender_ageonly work withperiod=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;
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;
}
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)