Integrating Instagram Graph API with AWS Lambda: Connecting @sailjada Photos to Guest Event Pages
What Was Done
We activated the dormant Instagram integration in the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to surface @sailjada Instagram posts alongside guest-uploaded charter photos on event pages at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The Lambda previously returned an empty array when Instagram credentials were missing; we established the correct OAuth token flow through Facebook's Instagram Graph API to populate those credentials.
Technical Details: The Credential Flow
Step 1: Provisioning the Correct API Product
The first blocker was product misconfiguration. The app sailjada-social had Instagram Messaging configured, which grants permissions for direct message handling but not for reading media. This is a critical distinction in Facebook's permission model.
- Navigated to
developers.facebook.com/apps→sailjada-socialapp dashboard - Added the Instagram Graph API product (distinct from Basic Display and Messaging variants)
- This product grants the
instagram_basicscope required to enumerate media from a business/creator account
Step 2: OAuth Token Generation via Graph API Explorer
Rather than building a full OAuth redirect flow for this internal integration, we used Facebook's Graph API Explorer to generate a short-lived access token with the necessary scopes.
- Opened
developers.facebook.com/tools/explorerin authenticated state - Selected the
sailjada-socialapp from the dropdown - Generated an Access Token with explicit scopes:
instagram_basicandpages_show_list - Selected the Facebook Page linked to the @sailjada business account as the token context
This short-lived token (valid for ~2 hours) was then used for the subsequent API calls to retrieve identifiers needed for long-lived token exchange.
Step 3: Retrieving the Instagram User ID
The Instagram Graph API requires an IG_USER_ID (the numeric business account identifier, not the handle). We retrieved this through two chained API calls using the short-lived token:
# First call: Get the Facebook Page's Instagram business account
curl -X GET "https://graph.instagram.com/v18.0/{page_id}?fields=instagram_business_account&access_token={short_lived_token}"
# Response structure:
# {
# "instagram_business_account": {
# "id": "{IG_USER_ID}"
# },
# "id": "{page_id}"
# }
# Second call: Verify account fields and media endpoints
curl -X GET "https://graph.instagram.com/v18.0/{IG_USER_ID}?fields=name,biography,media_count&access_token={short_lived_token}"
The IG_USER_ID extracted from the first response is a permanent identifier tied to the @sailjada account and does not expire.
Step 4: Long-Lived Token Exchange
Short-lived tokens are unsuitable for Lambda environment variables (they expire within hours). We exchanged the short-lived token for a long-lived token valid for 60 days using the app credentials:
curl -X GET "https://graph.instagram.com/v18.0/access_token?grant_type=ig_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&access_token={short_lived_token}"
# Response:
# {
# "access_token": "{long_lived_token}",
# "token_type": "bearer"
# }
This long-lived token becomes the IG_ACCESS_TOKEN environment variable.
Infrastructure Changes
Lambda Configuration Update
Updated the shipcaptaincrew Lambda function environment variables:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables="{IG_USER_ID={value},IG_ACCESS_TOKEN={value}}" \
--no-cli-pager
These variables are now available to the Lambda runtime via os.environ in the Python handler. The function checks for their presence and gracefully falls back to an empty array if missing (maintaining backward compatibility).
Code Path in Lambda Handler
The handler in shipcaptaincrew (exact path varies, but typically lambda_function.py at the root) contains logic like:
import os
import json
import boto3
ig_user_id = os.environ.get('IG_USER_ID')
ig_access_token = os.environ.get('IG_ACCESS_TOKEN')
def get_instagram_posts(event_date):
if not ig_user_id or not ig_access_token:
return [] # Graceful fallback
# Build Graph API request for media within event date range
# Example: fetch recent 20 posts from IG_USER_ID
# Filter by timestamp to match event window
return posts_list
Key Decisions
Why Long-Lived Tokens + 60-Day Refresh Strategy
Long-lived tokens are the correct pattern for backend integrations. Short-lived tokens require continuous user interaction, which is impractical for Lambda. The 60-day expiration window is Facebook's maximum for this grant type. We chose a manual refresh strategy over EventBridge automation initially to reduce operational complexity—the token refresh call can be triggered manually or via a future CloudWatch Events rule if automated renewal becomes necessary.
Graph API Explorer vs. Custom OAuth App
For a production photo integration serving guests, a full OAuth redirect flow would be cleaner. However, because @sailjada is the sole Instagram account feeding this system and the integration is internal to Ship Captain Crew operations, using the Graph API Explorer to bootstrap credentials is pragmatic. If the system expands to accept multiple Instagram sources, refactoring to a proper OAuth app flow would be warranted.
Permission Scopes
instagram_basic is the minimal scope needed to read business account media metadata. pages_show_list is required to enumerate linked Instagram accounts. We avoided requesting instagram_manage_messages or other elevated scopes—the app already had messaging capability and that was a red herring in the initial setup.
What's Next
- Token Refresh Automation: Implement an AWS EventBridge rule that triggers monthly and calls an internal Lambda to exchange the current token for a new 60-day token. Store the refreshed token back in AWS Secrets Manager or Parameter Store rather than directly in environment variables.
- Filtering and Deduplication: Enhance the Lambda to filter Instagram posts by timestamp (matching the event date/time window) and exclude posts already shown in the guest upload feed.
- Error Handling: Add CloudWatch Logs and alarm thresholds for Graph API call failures (rate limits, token expiry, account suspension).
- Testing at Scale: Verify the integration at the event page
/g