Integrating Instagram Graph API with AWS Lambda: Connecting Guest Photos to Ship Captain Crew Events
What Was Done
We enabled Instagram Graph API integration for the Ship Captain Crew guest photo system to automatically surface @sailjada Instagram posts alongside user-uploaded charter photos. The Lambda function at shipcaptaincrew (us-east-1, account 782785212866) previously had dormant Instagram integration code that returned empty arrays due to missing environment variables. This post walks through the complete setup process: creating the correct Facebook app product configuration, obtaining API credentials, exchanging tokens for long-lived access, and deploying credentials to Lambda.
Technical Details: The Instagram Graph API Setup Flow
Step 1: Add Instagram Graph API Product (Not Messaging)
The critical first mistake to avoid: adding the "Manage messaging" use case grants Direct Message capabilities, not media read permissions. Instead:
- Navigate to developers.facebook.com/apps
- Select the
sailjada-socialapplication - Click Add Product in the left sidebar (bottom section)
- Search for and select Instagram Graph API (distinct from Basic Display and Messaging)
- Complete the setup wizard—the product will appear in your left sidebar
Why this matters: The Graph API product is the only one that exposes the instagram_basic scope, required to query media data via the /ig_hashtag_search and /media endpoints. Messaging products restrict you to conversation endpoints.
Step 2: Verify @sailjada Account Type and Link to Facebook Page
Instagram Graph API requires a Business or Creator account linked to a Facebook Page. Verify this in the Instagram app settings, then in your app's Instagram Graph API section:
- Click API Setup with Instagram Login
- Click Add Instagram Account
- Authenticate as @sailjada
- Authorize the app to access account data
Step 3: Generate Short-Lived Access Token
The Graph API Explorer provides an interactive way to generate initial tokens with the correct scopes:
- Open developers.facebook.com/tools/explorer
- Switch the app selector to
sailjada-social - Click Generate Access Token
- Select the Facebook Page linked to @sailjada
- In the permissions prompt, grant:
instagram_basicandpages_show_list - Copy the resulting token (valid for ~2 hours)
Why short-lived first: Short-lived tokens are safer for initial testing and don't require storing credentials in your Lambda before you verify the workflow works end-to-end.
Step 4: Retrieve IG_USER_ID
The Lambda function needs the numeric Instagram Business Account ID (distinct from the username). Retrieve it via two API calls:
# First, get your Facebook Page ID and its linked Instagram account
curl -s "https://graph.instagram.com/me/accounts?access_token=SHORT_LIVED_TOKEN" \
| jq '.data[0].id'
# Then fetch the Instagram Business Account ID from that page
curl -s "https://graph.instagram.com/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN" \
| jq '.instagram_business_account.id'
Store this numeric ID as IG_USER_ID.
Step 5: Exchange for Long-Lived Token (60-Day Validity)
Short-lived tokens expire quickly. Exchange it for a long-lived token using your app credentials:
curl -s "https://graph.instagram.com/access_token" \
-d "grant_type=fb_exchange_token" \
-d "client_id=CLIENT_ID" \
-d "client_secret=CLIENT_SECRET" \
-d "access_token=SHORT_LIVED_TOKEN"
The response includes a new access_token field—this is your IG_ACCESS_TOKEN, valid for ~60 days. Store this securely in AWS Systems Manager Parameter Store or Secrets Manager (though our current setup uses Lambda environment variables).
Infrastructure: Lambda Configuration
The shipcaptaincrew Lambda function (runtime: Python 3.x, handler: lambda_function.handler in the deployment package) expects two environment variables:
IG_USER_ID: The numeric Instagram Business Account IDIG_ACCESS_TOKEN: The 60-day long-lived access token
Update the function configuration using AWS CLI:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables="{IG_USER_ID=YOUR_USER_ID,IG_ACCESS_TOKEN=YOUR_TOKEN}"
The Lambda code (located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py) contains the handler function which:
- Parses incoming requests to extract
event_idfrom paths like/g/2026-04-29 - Queries approved guest photos from the DynamoDB table
- Calls Instagram Graph API (if credentials are present) to fetch @sailjada posts from the same date window
- Merges and returns both datasets as JSON
Architecture Pattern: Dormant Feature Gate
The Instagram integration uses a defensive pattern: if environment variables are missing or invalid, the function returns an empty array for Instagram posts rather than failing the entire request. This allows the guest photo system to function independently while Instagram integration is optional. Once credentials are set, the API automatically enables the feature without code changes.
Key Decisions
- Long-lived tokens over manual refresh: Rather than implementing EventBridge-triggered token refresh logic immediately, we're using the 60-day window. Production can add automatic monthly refresh later.
- Environment variables over Secrets Manager: For simplicity in this phase, credentials are stored as Lambda environment variables. For higher security, migrate to AWS Secrets Manager and update the handler to retrieve values at runtime.
- Business Account linking: @sailjada must use a Business or Creator account (not a personal account) to appear in Graph API queries. Personal accounts cannot be accessed via this endpoint.
Verification
After updating Lambda configuration, test the integration by visiting the guest photo page:
curl -s "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29" | jq .
Check CloudWatch Logs for the Lambda