Integrating Instagram Graph API with Lambda: Connecting Guest Charter Photos to Social Media
What Was Done
We integrated Instagram Graph API into the ShipCaptainCrew guest photo gallery system to automatically surface @sailjada Instagram posts alongside user-uploaded charter photos. The guest page at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29) now has the infrastructure to pull Instagram media from a configurable time window, displaying both sources in a unified gallery.
The work involved three key areas: registering the correct Instagram Graph API product in the Facebook Developer App, establishing an OAuth token exchange flow, and deploying environment variables to the Lambda function that renders the guest page.
Technical Details
Application Structure
The guest photo system is deployed across two components:
- Lambda Function:
/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py— handles API requests, database queries for approved guest photos, and conditional Instagram media fetching - Frontend:
/Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html— static HTML/JavaScript that queries the Lambda endpoint and renders the combined gallery
The Lambda function exposes a route at /g/{event_id} that returns JSON with two arrays: approved guest photos from DynamoDB and Instagram posts from the Graph API (when credentials are present).
Instagram Graph API Product Registration
The initial setup required adding the correct product type to the Facebook Developer app (ID: sailjada-social). This was critical because the app had a "Messaging" product configured, which grants Direct Message scopes but not media-read permissions.
The required product: Instagram Graph API (distinct from Basic Display and Messaging).
Steps taken:
- Navigate to
developers.facebook.com/apps→ Select appsailjada-social - Left sidebar → Add Product
- Find Instagram in the product catalog → Click Set Up
- Select Instagram Graph API as the access type
- Once added, the product appears in the sidebar under Instagram Graph API
Business Account Linking
The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Business Page for Graph API access. Within the Instagram Graph API settings:
- Navigate to API Setup → Instagram Login
- Click Add Instagram Account
- Log in with @sailjada credentials to authorize the app
- Select the Business Page associated with @sailjada
Token Generation Flow
Instagram Graph API requires two tokens: a short-lived token (valid for 1 hour) generated during development, and a long-lived token (valid for 60 days) for production use.
Short-lived token generation:
- Visit
developers.facebook.com/tools/explorer - Select app dropdown → choose
sailjada-social - Click Generate Access Token
- Select the Facebook Business Page linked to @sailjada
- Confirm scopes:
instagram_basic,pages_show_list - Copy the generated token
Retrieving IG_USER_ID:
With the short-lived token, query the Facebook Graph API to find the Instagram Business Account ID:
curl -s "https://graph.instagram.com/me?fields=instagram_business_account&access_token=TOKEN" | jq
Response structure:
{
"instagram_business_account": {
"id": "17841401234567890"
},
"id": "123456789"
}
Extract the instagram_business_account.id value as IG_USER_ID.
Long-lived token exchange:
Exchange the short-lived token for a long-lived token valid for 60 days:
curl -s "https://graph.instagram.com/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&access_token=SHORT_LIVED_TOKEN" | jq
Response contains a new access_token with extended validity. Store this as IG_ACCESS_TOKEN.
Infrastructure & Deployment
Lambda Function Configuration
The Lambda function shipcaptaincrew in region us-east-1 (account 782785212866) was updated with environment variables:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables={IG_USER_ID=value,IG_ACCESS_TOKEN=value}
Before this change, the Lambda had stubbed Instagram support: if either environment variable was missing, the function returned an empty Instagram array.
The relevant Lambda code path (in lambda_function.py) checks for these variables at request time and makes a conditional call to the Instagram Graph API endpoint:
GET https://graph.instagram.com/{IG_USER_ID}/media?access_token={IG_ACCESS_TOKEN}
Logging & Verification
After deployment, logs were tailed to verify successful API calls:
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow
The guest page was tested by making a direct request to verify both guest photos and Instagram posts appear:
curl -s "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29" | jq
Key Decisions
- 60-day token strategy: Rather than implementing a full OAuth refresh flow, we opted for long-lived tokens with a monthly manual refresh. This reduces Lambda cold-start complexity and avoids storing refresh tokens in environment variables.
- Conditional Instagram fetching: The Lambda only calls Instagram Graph API if both credentials are present. This allows the guest page to degrade gracefully (showing only guest photos) if credentials are missing or expired.
- Time-window filtering: Instagram posts are filtered client-side in
index.htmlto match the event date. This avoids querying Instagram's full media history repeatedly. - Environment variables over secrets: While AWS Secrets Manager would be more secure, environment variables on the Lambda are sufficient for read-only API credentials with time-limited validity.