Integrating Instagram Graph API with AWS Lambda: Building a Guest Photo Gallery with Social Media Cross-Posts
What Was Done
We implemented Instagram Graph API integration into an existing AWS Lambda function that powers a guest photo gallery system at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The system displays guest-uploaded charter photos alongside official @sailjada Instagram posts from matching time windows, creating a unified social feed experience without requiring manual aggregation.
The core changes involved:
- Configuring the Instagram Graph API product within the Facebook App Dashboard for the
sailjada-socialapp - Establishing token exchange flows to obtain long-lived access tokens (60-day validity)
- Modifying the Lambda function handler to query Instagram's media endpoints when credentials are present
- Creating environment variable infrastructure to persist API credentials securely
- Building a token refresh strategy for sustained API access without manual intervention
Technical Details: Architecture and Implementation
The Problem Space
The existing guest photo page at /tools/shipcaptaincrew/ had a dormant Instagram integration layer. The Lambda function contained conditional logic that checked for IG_USER_ID and IG_ACCESS_TOKEN environment variables, but returned an empty array when these weren't configured. This allowed us to build the integration incrementally without disrupting the existing guest upload and display functionality.
Product Configuration in Facebook App Dashboard
The critical first step was selecting the correct API product. The sailjada-social app had Instagram Messaging configured (useful for DM automation), but that product doesn't grant the instagram_basic scope required to read media objects. We added Instagram Graph API as a separate product:
- Facebook App Dashboard → Add Product
- Search for Instagram → Select Instagram Graph API (not Basic Display, not Messaging)
- Complete the setup flow which enables the API in the left sidebar
This distinction matters: Basic Display has stricter rate limits and fewer fields; Messaging is for inbox operations; Graph API is the full-featured media endpoint suite.
Account Linking and Token Generation
Once the product was added, we connected the @sailjada Instagram business account through the Instagram Graph API setup panel. This step requires the account to be linked to a Facebook Page (which it was—Queen of San Diego).
The token generation flow uses the Facebook Graph API Explorer:
1. Visit developers.facebook.com/tools/explorer
2. Select app: sailjada-social
3. Generate new access token
4. Select the FB Page linked to @sailjada
5. Add scopes: instagram_basic, pages_show_list
This generates a short-lived user token (valid for ~2 hours). We then use this token to make two sequential API calls:
# Call 1: Get the FB Page's instagram_business_account ID
curl -s "https://graph.facebook.com/v19.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"
# Returns: { "instagram_business_account": { "id": "IG_USER_ID" }, "id": "PAGE_ID" }
# Call 2: Exchange short-lived token for long-lived token
curl -s "https://graph.facebook.com/v19.0/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&fb_exchange_token={SHORT_LIVED_TOKEN}"
# Returns: { "access_token": "IG_ACCESS_TOKEN", "token_type": "bearer" }
The long-lived token is valid for 60 days and can be refreshed by repeating Call 2 before expiration.
Infrastructure: Lambda Environment Variables and Configuration
The Lambda function in us-east-1 (account 782785212866) required two new environment variables:
IG_USER_ID— The numeric Instagram Business Account ID (e.g.,17841400000000000)IG_ACCESS_TOKEN— The 60-day long-lived token for API requests
These are configured via AWS Lambda Update Function Configuration:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables={IG_USER_ID=YOUR_ID,IG_ACCESS_TOKEN=YOUR_TOKEN}
The function name is shipcaptaincrew and the handler is defined in /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py.
Lambda Function Changes
Within the handler, we added a conditional block that queries the Instagram Graph API when credentials are present:
if IG_USER_ID and IG_ACCESS_TOKEN:
ig_posts = fetch_instagram_media(IG_USER_ID, IG_ACCESS_TOKEN, event_date_range)
else:
ig_posts = []
The fetch_instagram_media() function constructs the Graph API request:
GET https://graph.instagram.com/v19.0/{IG_USER_ID}/media
?fields=id,caption,media_type,media_url,timestamp
&access_token={IG_ACCESS_TOKEN}
We filter results by timestamp to match the event window (e.g., all posts from 2026-04-29 between 09:00 and 17:00 UTC for the /g/2026-04-29 route).
Key Decisions and Rationale
Why 60-Day Tokens Instead of Perpetual Credentials
Instagram Graph API enforces token expiration to reduce the blast radius of compromised credentials. A 60-day window is short enough for security (if a token leaks, the exposure is bounded) but long enough to avoid monthly manual rotations. We planned for automated refresh via an EventBridge rule that runs a Lambda trigger monthly, 10 days before expiration.
Environment Variables vs. Secrets Manager
For development and low-stakes operations, environment variables are pragmatic. For production, we recommend migrating to AWS Secrets Manager or Parameter Store to enable automatic rotation, audit logging, and encryption at rest. The conditional check in the handler allows graceful degradation—if credentials are absent, guests still see uploaded photos, just no Instagram cross-posts.
Filtering by Event Date/Time Window
Rather than fetching all Instagram posts and filtering client-side, we filter at the Lambda layer using the event_id timestamp. This reduces payload size, decreases frontend rendering time, and keeps the GraphQL query efficient.
What's Next
- Automated Token Refresh: Deploy an EventBridge rule (daily at 00:00 UTC) that invokes a refresh Lambda, extracting the