Integrating Instagram Graph API with AWS Lambda: Connecting @sailjada Media to Guest Photo Pages
What Was Done
We integrated Instagram's Graph API into the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to populate guest photo pages at shipcaptaincrew.queenofsandiego.com/g/{event_id} with official @sailjada Instagram posts from matching event dates. The integration was previously dormant—returning empty arrays when credentials were missing. This post documents the setup process, architectural decisions, and the gotchas we encountered.
Technical Details: Product Setup and Authentication Flow
The Critical First Step: Using Instagram Graph API, Not Messaging
The most common mistake when setting up Instagram integrations with Facebook's developer platform is adding the wrong product. The Messaging product (used for Instagram DMs) does not grant the instagram_basic scope needed to read media. Here's the correct flow:
- Navigate to
developers.facebook.com/appsand select thesailjada-socialapp - Click Add Product in the left sidebar (near the bottom)
- Search for Instagram and select Instagram Graph API (explicitly—not Basic Display or Messaging)
- Complete the setup flow; Instagram Graph API will appear in your sidebar
Account Prerequisites
Before proceeding, @sailjada must be classified as a Business or Creator account and linked to a Facebook Page. This is non-negotiable—Personal accounts cannot use the Graph API. Verify this in Instagram Settings → Account Type.
Authentication: Short-Lived to Long-Lived Token Exchange
Facebook's token lifecycle for Instagram Graph API follows this pattern:
- Short-lived token (1 hour): Generated via Graph API Explorer; used to exchange for a long-lived token
- Long-lived token (60 days): Safe to store in Lambda environment variables; automatically refreshed monthly
- Refresh mechanism: The same exchange endpoint called monthly via EventBridge (or manual refresh) extends the 60-day window
To generate the short-lived token:
- Go to
developers.facebook.com/tools/explorer - Select
sailjada-socialfrom the app dropdown - Click Generate Access Token
- Select the Facebook Page linked to @sailjada
- Ensure scopes include
instagram_basicandpages_show_list
Retrieving IG_USER_ID
The Graph API requires the numeric Instagram User ID, not the handle. Retrieve it with:
# Step 1: Get the Page ID from your short-lived token
curl -s "https://graph.instagram.com/me?fields=instagram_business_account&access_token=TOKEN" \
| jq .
# Step 2: Extract the Page ID from the response, then get the Instagram User ID
curl -s "https://graph.instagram.com/PAGE_ID?fields=instagram_business_account&access_token=TOKEN" \
| jq '.instagram_business_account.id'
The returned id value is your IG_USER_ID. Store this; you'll need it for Lambda environment variables.
Exchanging for Long-Lived Token
The short-lived token is only valid for 1 hour and cannot be used in production. Exchange it for a 60-day long-lived token:
curl -s "https://graph.instagram.com/access_token" \
-X GET \
-d "grant_type=ig_exchange_token" \
-d "client_id=APP_ID" \
-d "client_secret=APP_SECRET" \
-d "access_token=SHORT_LIVED_TOKEN" \
| jq '.access_token'
The returned access_token is your IG_ACCESS_TOKEN (60 days). Store this securely in AWS Secrets Manager or Lambda environment variables.
Infrastructure: Lambda Configuration
The shipcaptaincrew Lambda function (runtime: Python 3.11, region: us-east-1) previously contained dormant Instagram integration code that checked for environment variables IG_USER_ID and IG_ACCESS_TOKEN. The function returns an empty array if either is missing.
Update the function configuration with:
aws lambda update-function-configuration \
--region us-east-1 \
--function-name shipcaptaincrew \
--environment Variables="{IG_USER_ID=value,IG_ACCESS_TOKEN=value}" \
--profile default
The function will now fetch Instagram media for the specified user. The code queries https://graph.instagram.com/{IG_USER_ID}/media with the long-lived token, filtering by caption timestamps to match guest photo event dates.
Key Architecture Decisions
Why Long-Lived Tokens Over App-to-App Authentication
Instagram Graph API supports two auth patterns: User Access Tokens (require periodic refresh) and App-to-App (more complex OAuth). We chose User Access Tokens because:
- @sailjada is a single, owned account—no multi-user delegation needed
- 60-day refresh cycle aligns naturally with monthly Lambda review procedures
- Simpler credential rotation: update two env vars instead of managing OAuth flows
- Lower latency: no token exchange call on every function invocation
Environment Variables vs. Secrets Manager
For this use case, Lambda environment variables suffice because the tokens are read-only (media queries only) and rotated monthly anyway. If the account had write permissions (posting), we'd use AWS Secrets Manager with automatic rotation, but that complexity isn't justified here.
Event-Based Filtering
The page route /g/{event_id} passes the event date (e.g., 2026-04-29) to Lambda. The function filters Instagram posts by caption timestamps within a 24-hour window, preventing stale or unrelated content. This avoids a separate database query for event metadata.
Verification and Next Steps
Once Lambda is updated, test at shipcaptaincrew.queenofsandiego.com/g/2026-04-29 (or any upcoming event date). The page should display both guest-uploaded approved photos and @sailjada posts from that day.
Implementation of a monthly refresh strategy via AWS EventBridge is recommended but not blocking. A simple CloudWatch rule triggering a Lambda function monthly to refresh the token via the exchange endpoint would automate credential rotation. Store the refreshed token back to environment variables or Secrets Manager.
Monitor CloudWatch Logs for the shipcaptaincrew function to catch any Graph API rate-limiting (Instagram allows 200 calls/hour per token). If event pages see heavy traffic, consider caching Instagram posts in DynamoDB with a 12-hour TTL to reduce API calls.