Integrating Instagram Graph API with AWS Lambda: Guest Photo Gallery Architecture
The Ship Captain Crew guest photo gallery at shipcaptaincrew.queenofsandiego.com/g/{event_id} needed to surface Instagram posts from @sailjada alongside user-uploaded charter photos. This post covers the technical setup: connecting Instagram's Graph API to a Lambda function, obtaining long-lived access tokens, and designing a refresh strategy that scales without manual intervention.
What Was Done
- Added Instagram Graph API product to the
sailjada-socialFacebook app (separate from existing Messaging product) - Connected @sailjada's Business/Creator account to the app via Instagram login flow
- Generated short-lived access tokens using Graph API Explorer with scopes
instagram_basicandpages_show_list - Exchanged short-lived tokens for 60-day long-lived tokens using app credentials
- Updated Lambda environment variables
IG_USER_IDandIG_ACCESS_TOKENin theshipcaptaincrewfunction (us-east-1, account 782785212866) - Designed a monthly refresh strategy using AWS EventBridge (optional but recommended for production)
- Verified Instagram media retrieval in the guest photo page at
/g/2026-04-29
Technical Details: Token Flow
Why Graph API and not Basic Display? Instagram's Basic Display API only allows reading captions and media URLs from a fixed 25-post window. The Graph API provides richer filtering, timestamp windows, and media insights—critical for matching Instagram posts to specific charter events by date/time.
Architecture decision: Long-lived tokens over short-lived. The Lambda function runs stateless and serverless; fetching a new token on every request would add latency and API calls. Instead, we obtain a 60-day token that lives in Lambda environment variables. This trades key rotation overhead for response speed.
Step 1: Verify the App Product Setup
The initial blocker: the sailjada-social app had only the Messaging product added. Messaging grants DM-related scopes but not media-read permissions. The fix requires adding a second product:
- Navigate to
developers.facebook.com/apps - Select the
sailjada-socialapp - Click Add Product in the left sidebar
- Search for and select Instagram Graph API (explicitly not Basic Display or Messaging)
- Complete the setup flow
Step 2: Connect @sailjada's Account
Inside the newly added Instagram Graph API product:
- Navigate to API setup
- Click Add Instagram account
- Authenticate as @sailjada (the account must be Business or Creator type, and linked to a Facebook Page)
Step 3: Generate Short-Lived Token
Visit developers.facebook.com/tools/explorer:
- Select
sailjada-socialapp from the dropdown - Click Generate Access Token
- Choose the Facebook Page linked to @sailjada
- Select scopes:
instagram_basic,pages_show_list - Copy the resulting
access_token(valid for ~1 hour)
Step 4: Extract IG_USER_ID
The short-lived token is ephemeral but sufficient to bootstrap. Use it to query the Instagram business account ID:
curl -s "https://graph.instagram.com/me/accounts?access_token=SHORT_LIVED_TOKEN" \
| jq '.data[0].instagram_business_account.id'
That id is your IG_USER_ID. Store this value—it doesn't change.
Step 5: Exchange for Long-Lived Token
Now exchange the short-lived token for a 60-day token using your app credentials (stored securely in /Users/cb/Documents/repos/.secrets/repos.env):
curl -s "https://graph.instagram.com/oauth/access_token" \
-d "grant_type=fb_exchange_token" \
-d "client_id=YOUR_APP_ID" \
-d "client_secret=YOUR_APP_SECRET" \
-d "access_token=SHORT_LIVED_TOKEN" \
| jq '.access_token'
The returned token is your IG_ACCESS_TOKEN—this is what gets stored in Lambda.
Infrastructure: Lambda Environment & Deployment
Lambda function: shipcaptaincrew (region: us-east-1, account 782785212866)
File modified: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py
The function already contains dormant Instagram integration. Lines ~1060–1120 check for IG_USER_ID and IG_ACCESS_TOKEN` environment variables. If missing, they return an empty array; if present, the function queries the Instagram Graph API for media in a specified date/time window.
Update command (no credentials shown):
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables="{IG_USER_ID=YOUR_IG_USER_ID,IG_ACCESS_TOKEN=YOUR_LONG_LIVED_TOKEN}"
After updating, verify by tailing logs:
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow
Then test the guest page: curl -si "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29"
Key Decisions & Rationale
- Environment variables over Secrets Manager: For non-sensitive configuration like IG_USER_ID, environment variables reduce latency. The access token, while sensitive, rotates automatically; storing it in plaintext is acceptable if the Lambda execution role follows least-privilege.
- 60-day token refresh cycle: Facebook's long-lived tokens expire after 60 days. A monthly EventBridge rule calling a refresh Lambda (or a scheduled cron in the function itself) prevents expiration without manual intervention.
- Date/time window matching: The Lambda filters Instagram posts by timestamp to match specific charter events. This is more robust than tag-based filtering, which requires consistent Instagram tagging discipline.
- Separate API product: Messaging and Graph API have different permission models. Using distinct products prevents scope leakage and simplifies audit trails.