Integrating Instagram Graph API with AWS Lambda: Connecting Guest Photo Events to Social Media
Overview
The Ship Captain Crew guest photo system needed to aggregate user-uploaded charter photos alongside official @sailjada Instagram posts from matching event dates. This required adding Instagram Graph API integration to an existing AWS Lambda function deployed at shipcaptaincrew.queenofsandiego.com. The challenge: properly configuring Facebook App permissions, obtaining long-lived access tokens, and maintaining them across Lambda function lifecycles.
What Was Done
We completed the Instagram Graph API integration pipeline for the guest photo system by:
- Added Instagram Graph API product to the existing
sailjada-socialFacebook App (previously only had Messaging API configured) - Connected the
@sailjadaInstagram Business account and obtained its correspondingIG_USER_ID - Generated and exchanged temporary access tokens for a long-lived token (60-day validity)
- Updated Lambda environment variables
IG_USER_IDandIG_ACCESS_TOKENin the shipcaptaincrew function - Modified the Lambda handler to conditionally query Instagram media when tokens are available
Technical Details: Why the Messaging API Wasn't Enough
Initial investigation found that the sailjada-social app only had the Instagram Messaging API configured. While this grants permissions for Direct Messages, it does not include the instagram_basic scope required to read media. The Graph API product needed to be added separately with explicit permissions for media access.
Why this matters: Facebook App permissions are granular. A single product (Messaging, Basic Display, Graph API) grants specific scopes. The Messaging API is designed for chatbots; it never grants media read permissions. The Instagram Graph API product is required for reading posts, captions, and media data.
Configuration Steps and Infrastructure Changes
Step 1: Add Instagram Graph API Product
- Navigate to
developers.facebook.com/apps - Select app
sailjada-social - In the left sidebar, click Add Product
- Search for "Instagram" and select Instagram Graph API (not Basic Display, not Messaging)
- Complete setup — this configures the Graph API permission model for the app
Step 2: Connect the Instagram Business Account
The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Page. This is a prerequisite for Graph API access.
Inside the app dashboard, under Instagram Graph API → API Setup:
- Click Add Instagram account
- Authenticate as
@sailjada - Authorize the
sailjada-socialapp to access the account
Step 3: Generate Short-Lived Token
Using the Facebook Graph API Explorer:
# Navigate to developers.facebook.com/tools/explorer
# 1. Select app: sailjada-social
# 2. Click "Generate Access Token"
# 3. Select the Facebook Page linked to @sailjada
# 4. Ensure scopes include: instagram_basic, pages_show_list
# 5. Copy the short-lived token (valid ~2 hours)
Step 4: Retrieve IG_USER_ID
The Instagram User ID is nested inside the Facebook Page's instagram_business_account field. Two API calls extract this:
# First: Get the Page ID (if not already known)
curl -s "https://graph.facebook.com/v18.0/me?access_token=TOKEN" | jq '.id'
# Second: Get the Instagram Business Account ID
curl -s "https://graph.facebook.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token=TOKEN" \
| jq '.instagram_business_account.id'
# Result: This ID is your IG_USER_ID (e.g., "17841400000000000")
Step 5: Exchange for Long-Lived Token
Short-lived tokens expire in ~2 hours. The Graph API provides an exchange endpoint to obtain a 60-day token:
curl -s "https://graph.instagram.com/access_token" \
-d "grant_type=ig_refresh_token" \
-d "access_token=TOKEN" \
| jq '.access_token'
# The returned access_token is your IG_ACCESS_TOKEN (60-day validity)
Why 60 days and not permanent? Instagram intentionally limits long-lived tokens to encourage refresh workflows. This prevents leaked tokens from remaining valid indefinitely. A 60-day window requires quarterly or monthly refresh cycles.
Step 6: Update Lambda Environment Variables
The shipcaptaincrew Lambda function in us-east-1 (account 782785212866) stores credentials as environment variables:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables="{IG_USER_ID=17841400000000000,IG_ACCESS_TOKEN=IGAAxxxxxxxxxxxx...}"
File affected: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py
The handler checks for these variables at runtime:
ig_user_id = os.environ.get('IG_USER_ID')
ig_token = os.environ.get('IG_ACCESS_TOKEN')
if ig_user_id and ig_token:
# Query Instagram media for the event date window
ig_posts = fetch_instagram_posts(ig_user_id, ig_token, event_date)
else:
# Return empty array; integration inactive
ig_posts = []
Why This Architecture?
- Environment variables for secrets: Lambda's built-in encryption at rest protects credentials without adding a separate secrets manager. For production, AWS Secrets Manager would be ideal, but env vars are sufficient for this non-public endpoint.
- Conditional logic in handler: The function degrades gracefully if tokens are missing. Guest photos display regardless; Instagram content is supplementary.
- 60-day refresh cycle: Quarterly refreshes via manual command or EventBridge automation prevent token expiry without rotating credentials across the entire stack.
- Graph API over Basic Display: Graph API provides richer metadata (captions, timestamps, media URLs) needed to cross-reference Instagram posts with guest event dates.
Testing and Verification
After updating Lambda configuration, verify the integration:
# Check CloudWatch logs for the shipcaptaincrew function
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow
# Test the guest page endpoint
curl -si "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29"
The response should include both approved guest photos and