Instagram Reels Insights Implementation Plan
Table of Contents
- Executive Summary
- Current Architecture Analysis
- Problems & Gaps
- Focus Points for Implementation
- New Architecture
- Implementation Roadmap
Executive Summary
This document outlines the implementation plan for fetching Instagram Reels insights via the Meta Graph API. The current implementation uses the deprecated Instagram Basic Display API which does NOT support insights. The fix requires migrating to Instagram Graph API with proper OAuth flow and permissions.
Key Decisions (Confirmed via Meta Documentation)
- API: Instagram Graph API (graph.facebook.com) - NOT Basic Display API
- Auth: Instagram Login (Business Login) - Facebook Login not required
- Permission:
instagram_business_manage_insights - Token: Long-lived tokens valid for 60 days (with refresh capability)
- Rate Limit: 200 requests/hour per creator token
- Follower Requirement: None - all Creator/Business accounts eligible
Current Architecture Analysis
Existing Flow
┌─────────────────────────────────────────────────────────────────────────┐
│ CURRENT ARCHITECTURE │
└─────────────────────────────────────────────────────────────────────────┘
┌──────────┐ ┌──────────────────────┐ ┌─────────────────────┐
│ Creator │────▶│ Instagram OAuth │────▶│ Store Token │
│ │ │ (api.instagram.com) │ │ in CreatorProfile │
└──────────┘ └──────────────────────┘ └─────────────────────┘
│ │
│ Uses: │
│ - user_profile │
│ - user_media │
│ (DEPRECATED - NO INSIGHTS) │
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ Cron: view-sync.ts │
│ - Fetches content URLs from participations │
│ - Calls instagram.ts service │
│ - Stores in ViewSnapshot │
└──────────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Current Issues Found │
│ - instagram.ts uses api.instagram.com (Basic Display - DEPRECATED) │
│ - Scope: user_profile, user_media (no insights permission) │
│ - No token refresh logic │
│ - No App Review submitted │
└──────────────────────────────────────────────────────────────────────┘
Current File Locations
| File | Purpose | Issue |
|---|---|---|
src/app/api/auth/instagram/route.ts | OAuth initiate | Wrong API endpoint |
src/app/api/auth/instagram/callback/route.ts | OAuth callback | Wrong token exchange |
worker/src/services/instagram.ts | Fetch metrics | Deprecated API |
worker/src/crons/view-sync.ts | Cron job | Works (uses whatever token) |
prisma/schema.prisma | Database | Has ViewSnapshot model |
Problems & Gaps
Critical Issues
| # | Problem | Impact | Priority |
|---|---|---|---|
| 1 | Using deprecated Instagram Basic Display API | Insights will never work | 🔴 Critical |
| 2 | Wrong OAuth scope (user_profile, user_media) | No insights permission | 🔴 Critical |
| 3 | No token refresh logic | Tokens expire after 60 days | 🟠 High |
| 4 | No App Review submission | Won't work for public users | 🔴 Critical |
| 5 | Limited error handling for API failures | Silent failures | 🟡 Medium |
Data Gaps (What We Can't Get)
| Metric | Available? | Notes |
|---|---|---|
| Plays/Views | ✅ Yes | |
| Reach | ✅ Yes | |
| Likes/Comments/Saves/Shares | ✅ Yes | |
| Avg Watch Time | ✅ Yes | |
| Completion Rate | ✅ Yes | |
| Demographic (age/location per reel) | ❌ No | Account-level only |
| Automated guidelines check | ❌ No | Parse caption manually |
| Organic vs Promoted | ❌ No | Not available via API |
Focus Points for Implementation
Phase 1: OAuth & Authentication (Priority: 🔴 Critical)
-
Switch to Instagram Graph API
- Change from
api.instagram.com/oauth/authorize - To
graph.facebook.com/v18.0/dialog/oauth(or Instagram Business Login)
- Change from
-
Update Scopes
- Current:
user_profile,user_media - New:
instagram_business_manage_insights
- Current:
-
Token Exchange Flow
- Exchange code for short-lived token
- Exchange for long-lived token (60 days)
- Store long-lived token in
CreatorProfile.instagramAccessToken
Phase 2: Token Management (Priority: 🟠 High)
-
Implement Token Refresh
- Tokens valid 60 days
- Refresh when token is 24-59 days old
- Need cron job:
token-refresh.ts(already exists but check implementation)
-
Token Storage
- Already in
CreatorProfile.instagramAccessToken - Consider adding:
instagramTokenExpiresAt
- Already in
Phase 3: Insights Fetching (Priority: 🔴 Critical)
-
Update instagram.ts Service
- Use
graph.instagram.com/v18.0/{media-id}/insights - Request metrics:
plays,reach,likes,comments,saved,shares,ig_reels_avg_watch_time
- Use
-
Handle Rate Limits
- 200 requests/hour per creator token
- Implement caching strategy
- Batch requests where possible
Phase 4: Data Storage (Priority: 🟡 Medium)
-
ViewSnapshot Enhancements
- Current: viewCount, likeCount, commentCount, shareCount
- Add: reach, saves, avgWatchTime, completionRate
-
New Models to Consider
ReelInsights: Store granular reel metricsFraudFlag: Already exists - enhance fraud detection
Phase 5: API & Display (Priority: 🟡 Medium)
-
Brand View API
- Show campaign-specific reel analytics
- Filter by participation
-
Admin View API
- Full access to all metrics
- Fraud detection dashboard
- Token health status
Phase 6: App Review (Priority: 🔴 Critical)
- Submit for Meta App Review
- Required permission:
instagram_business_manage_insights - Provide demo video/screenshots
- Expected time: days to weeks
- Required permission:
New Architecture
Complete Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ NEW ARCHITECTURE │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 1: Creator Connects Instagram │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────┐ ┌────────────────────────────┐ ┌─────────────────────┐
│ Creator │────▶│ OAuth: graph.facebook.com │────▶│ Exchange for │
│ (Web) │ │ /dialog/oauth │ │ Long-lived Token │
└──────────┘ │ scope: instagram_ │ └─────────┬───────────┘
│ business_manage_insights │ │
└────────────────────────────┘ │
▼
┌─────────────────────────────┐
│ Store in DB │
│ CreatorProfile: │
│ - instagramAccessToken │
│ - instagramTokenExpiresAt │
│ - instagramUserId │
└─────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 2: Token Management (Cron Job) │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────┐
│ Cron: token-refresh │─────▶│ Check token age │
│ (every 24 hours) │ │ If 24-59 days old: │
└──────────────────────┘ │ Refresh via API │
│ Update in DB │
└──────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 3: Fetch Insights (Cron Job) │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────┐ ┌──────────────────────────┐
│ Cron: view-sync │─────▶│ For each participation │
│ (hourly) │ │ Get content URL │
└──────────────────────┘ └────────────┬─────────────┘
│
▼
┌──────────────────────────────┐
│ 1. Resolve Media ID │
│ (shortcode → numeric ID) │
└───────────────┬──────────────┘
│
▼
┌──────────────────────────────┐
│ 2. Fetch Insights │
│ GET /{media-id}/ │
│ insights?metric= │
│ plays,reach,likes, │
│ comments,saved,shares, │
│ ig_reels_avg_watch_time │
│ (using creator's token) │
└───────────────┬──────────────┘
│
▼
┌──────────────────────────────┐
│ 3. Store in ViewSnapshot │
│ + Calculate delta │
└───────────────┬──────────────┘
│
▼
┌──────────────────────────────┐
│ 4. Fraud Detection │
│ checkViewSpike() │
└──────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ STEP 4: Display via API │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ API Endpoints │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ /api/brand/campaigns/{id}/analytics │
│ ├── Returns: Campaign-specific metrics only │
│ ├── Metrics: views, likes, comments, saves, shares, reach │
│ ├── Auth: Brand who owns campaign │
│ └── Sample Response: │
│ { │
│ "creatorId": "...", │
│ "contentUrl": "...", │
│ "views": 15000, │
│ "likes": 450, │
│ "comments": 89, │
│ "saves": 120, │
│ "shares": 45, │
│ "reach": 12000, │
│ "avgWatchTime": 28.5, │
│ "completionRate": 0.72, │
│ "lastUpdated": "2026-03-14T10:00:00Z" │
│ } │
│ │
│ /api/admin/insights/{campaignId}/{creatorId} │
│ ├── Returns: Full metrics + fraud flags │
│ ├── Additional: token health, API rate limit status │
│ └── Auth: Admin only │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Database Models │
└─────────────────────────────────────────────────────────────────────────────┘
CreatorProfile (existing + new fields):
├── instagramAccessToken String (existing)
├── instagramUserId String? (NEW - for API calls)
├── instagramTokenExpiresAt DateTime? (NEW - for refresh logic)
├── instagramHandle String? (existing)
└── instagramFollowers Int? (existing)
ViewSnapshot (existing + new fields):
├── id String (existing)
├── campaignId String (existing)
├── creatorId String (existing)
├── platform Platform (existing)
├── postUrl String (existing)
├── viewCount Int (existing)
├── likeCount Int (existing)
├── commentCount Int (existing)
├── shareCount Int (existing)
├── deltaViews Int (existing)
├── isFlagged Boolean (existing)
├── snapshotAt DateTime (existing)
├── reach Int? (NEW)
├── saves Int? (NEW)
├── avgWatchTime Float? (NEW - seconds)
└── completionRate Float? (NEW - percentage)
Implementation Roadmap
Step-by-Step Tasks
Week 1: OAuth Fix (Critical Path)
| Task | File | Description |
|---|---|---|
| 1.1 | src/app/api/auth/instagram/route.ts | Change to Graph API auth URL |
| 1.2 | src/app/api/auth/instagram/route.ts | Update scope to instagram_business_manage_insights |
| 1.3 | src/app/api/auth/instagram/callback/route.ts | Update token exchange to Graph API |
| 1.4 | src/app/api/auth/instagram/callback/route.ts | Store instagramUserId and expiry |
| 1.5 | Test locally with test Instagram account | Verify OAuth works |
Week 2: Insights Fetching
| Task | File | Description |
|---|---|---|
| 2.1 | worker/src/services/instagram.ts | Update to use graph.instagram.com/v18.0 |
| 2.2 | worker/src/services/instagram.ts | Add metrics: reach, saves, avgWatchTime |
| 2.3 | worker/src/services/instagram.ts | Handle rate limit errors gracefully |
| 2.4 | prisma/schema.prisma | Add new fields to ViewSnapshot |
| 2.5 | Test with sample reel | Verify metrics returned |
Week 3: Token Management
| Task | File | Description |
|---|---|---|
| 3.1 | worker/src/crons/token-refresh.ts | Implement refresh logic |
| 3.2 | worker/src/crons/token-refresh.ts | Run daily to check token age |
| 3.3 | Add to worker/src/index.ts | Register cron if not done |
Week 4: API & Display
| Task | File | Description |
|---|---|---|
| 4.1 | Create brand analytics API | /api/brand/campaigns/{id}/insights |
| 4.2 | Create admin analytics API | /api/admin/insights/* |
| 4.3 | Update frontend | Show new metrics in dashboard |
| 4.4 | Add fraud indicators | Visual flags in admin panel |
Week 5: Testing & App Review
| Task | Description |
|---|---|
| 5.1 | End-to-end testing with multiple creators |
| 5.2 | Rate limit testing |
| 5.3 | Prepare App Review submission |
| 5.4 | Submit for Meta App Review |
| 5.5 | Deploy to staging |
Environment Variables Required
env
# Meta / Instagram
INSTAGRAM_CLIENT_ID=your_app_id
INSTAGRAM_CLIENT_SECRET=your_app_secret
INSTAGRAM_APP_ID=your_meta_app_id # For Graph API
# Existing (should already exist)
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=...
API Metrics Reference
Available Reels Metrics
| Metric | API Name | Description |
|---|---|---|
| Views | plays | Number of times reel played |
| Reach | reach | Unique accounts that saw the reel |
| Likes | likes | Total likes |
| Comments | comments | Total comments |
| Saves | saved | Times saved to collection |
| Shares | shares | Times shared via DM/Story |
| Avg Watch Time | ig_reels_avg_watch_time | Average seconds watched (in ms) |
| Completion Rate | ig_reels_avg_watch_time / video_duration | Calculated |
Sample API Call
GET https://graph.instagram.com/v18.0/{media-id}/insights
?metric=plays,reach,likes,comments,saved,shares,ig_reels_avg_watch_time
&access_token={creator_access_token}
Response
json
{
"data": [
{ "name": "plays", "period": "lifetime", "values": [{ "value": 15234 }] },
{ "name": "reach", "period": "lifetime", "values": [{ "value": 12500 }] },
{ "name": "likes", "period": "lifetime", "values": [{ "value": 892 }] },
{ "name": "comments", "period": "lifetime", "values": [{ "value": 45 }] },
{ "name": "saved", "period": "lifetime", "values": [{ "value": 156 }] },
{ "name": "shares", "period": "lifetime", "values": [{ "value": 78 }] },
{ "name": "ig_reels_avg_watch_time", "period": "lifetime", "values": [{ "value": 28500 }] }
]
}
Testing Checklist
- Creator can connect Instagram via new OAuth flow
- Long-lived token stored in database
- Insights fetched successfully for a test reel
- All metrics (plays, reach, likes, comments, saves, shares) returned
- ViewSnapshot updated with new metrics
- Brand can see campaign-specific analytics
- Admin can see full metrics + fraud flags
- Token refresh works (simulate 60-day expiry)
- Rate limit handling works (test with multiple calls)
Open Questions for Meta
- Does
instagram_business_manage_insightsrequire App Review for testing in Development mode? - Is there a way to test insights without App Review on a test Instagram account?
- What's the exact rate limit header to monitor usage?
Appendix: Related Files
viral-payouts/
├── src/
│ └── app/
│ └── api/
│ └── auth/
│ └── instagram/
│ ├── route.ts # OAuth initiate (NEEDS FIX)
│ └── callback/
│ └── route.ts # OAuth callback (NEEDS FIX)
├── worker/
│ └── src/
│ ├── services/
│ │ ├── instagram.ts # Fetch insights (NEEDS FIX)
│ │ ├── youtube.ts
│ │ └── fraud.ts
│ └── crons/
│ ├── view-sync.ts # Fetch metrics hourly
│ ├── token-refresh.ts # Refresh tokens (NEEDS IMPROVEMENT)
│ └── ...
└── prisma/
└── schema.prisma # Add new fields
Last Updated: 2026-03-14