Integrating Instagram Graph API with AWS Lambda: Authenticating @sailjada's Business Account for Guest Photo Feeds
What Was Done
We implemented Instagram Graph API authentication in the shipcaptaincrew Lambda function to surface @sailjada's Instagram posts alongside guest-uploaded charter photos on the event guest page at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration was previously dormant—returning empty arrays when environment variables were missing. This walkthrough documents the exact steps to activate it using a long-lived access token and establish a sustainable refresh strategy.
Technical Details: The Authentication Flow
Step 1: Add the Correct Product to Your Facebook App
The initial blocker was product selection. The app sailjada-social (ID: 1688884572116630) had the "Messaging" use case added, which grants the wrong OAuth scopes. Messaging is for DMs; we need media reads.
- Navigate to developers.facebook.com/apps
- Select sailjada-social
- Click Add Product in the left sidebar
- Search for Instagram and select Instagram Graph API (not Basic Display)
Step 2: Connect the Instagram Business Account
@sailjada must be a Business or Creator account linked to a Facebook Page. Once the product is added:
- In Instagram Graph API → API setup
- Click Add Instagram account
- Authenticate as @sailjada (the account owner)
Step 3: Generate a Short-Lived Token via Graph API Explorer
The short-lived token is a stepping stone to the long-lived token. It's good for ~1 hour and grants only the scopes you explicitly request.
- Go to developers.facebook.com/tools/explorer
- Select app dropdown → choose sailjada-social
- Click Generate Access Token
- In the permissions modal, select:
instagram_basic(read media, user profile)pages_show_list(enumerate linked pages)
- Confirm and copy the token
Step 4: Retrieve IG_USER_ID
With the short-lived token, query the Facebook Graph API to find the Instagram Business Account ID tied to your Facebook Page.
# Get the Facebook Page ID (the one connected to @sailjada)
curl -s "https://graph.facebook.com/v18.0/me/accounts?access_token=SHORT_LIVED_TOKEN" | jq '.data[] | {id, name}'
# Note the page_id from the response, then retrieve the Instagram account ID:
curl -s "https://graph.facebook.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN" | jq '.instagram_business_account.id'
The returned id is your IG_USER_ID. Example: 17841402023456789
Step 5: Exchange Short-Lived Token for Long-Lived Token
Long-lived tokens are valid for ~60 days and are suitable for backend services. This is what we store in Lambda environment variables.
curl -s "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=1688884572116630&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN" | jq '.access_token'
The returned access_token is your IG_ACCESS_TOKEN. Store this securely.
Infrastructure: Lambda Environment Configuration
File: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py
The handler checks for two environment variables at runtime. If both are set, it makes authenticated requests to the Instagram Graph API. If missing, it gracefully returns an empty array (the previous dormant state).
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_IG_ACCESS_TOKEN}"
Verify the update:
aws lambda get-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 | jq '.Environment.Variables'
Code Integration in Lambda
Within lambda_function.py, the guest page handler (around line 1060+) calls an internal function to fetch Instagram posts when IG_USER_ID and IG_ACCESS_TOKEN are present:
def fetch_instagram_posts(user_id, access_token, event_date):
"""Fetch Instagram media for the given date."""
if not user_id or not access_token:
return []
url = f"https://graph.instagram.com/v18.0/{user_id}/media"
params = {
'fields': 'id,caption,media_type,media_url,timestamp,permalink',
'access_token': access_token
}
response = requests.get(url, params=params)
if response.status_code != 200:
# Log the error, return empty
return []
# Filter posts by event_date window
posts = [...]
return posts
The guest page template (index.html) renders Instagram posts and guest photos side-by-side, fetched via API response.
Key Decisions
- Long-lived tokens over short-lived: Backend services need tokens that persist across multiple invocations. The 60-day window is appropriate for a scheduled monthly refresh strategy.
- Environment variables for secrets: Storing
IG_USER_IDandIG_ACCESS_TOKENas Lambda env vars allows us to rotate tokens without redeploying code. AWS Secrets Manager would be the next step for production hardening. - Graceful degradation: If env vars are missing, the page still renders guest photos. This keeps the feature non-blocking for deployments.
- Graph API v18.0: Pinning the API version ensures predictable field names and behavior across token refreshes.
What's Next: Token Refresh Strategy
Long-lived tokens expire after 60 days. Implement a monthly refresh:
- Option 1 (Manual):