Integrating Instagram Graph API with AWS Lambda: Building a Guest Photo Gallery with Social Media Cross-Publishing
What Was Done
We enhanced the Ship Captain Crew guest photo gallery system (shipcaptaincrew.queenofsandiego.com/g/{event_id}) to surface Instagram posts from the @sailjada account alongside user-uploaded charter photos. The integration required:
- Configuring the Facebook Developer app (
sailjada-social) with Instagram Graph API product - Implementing token exchange flows in the Lambda function to obtain and refresh long-lived access tokens
- Adding environment variable support for
IG_USER_IDandIG_ACCESS_TOKENin AWS Lambda - Building API query logic to fetch media from the Instagram Business Account within date/time windows
- Updating the front-end HTML to display both photo sources in a unified gallery
Technical Details: Architecture and Implementation
Lambda Function Updates
The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py was modified to support Instagram media retrieval. The function now includes:
- Token validation on startup: Checks for
IG_USER_IDandIG_ACCESS_TOKENenvironment variables; returns an empty array if either is missing, allowing the service to operate gracefully without Instagram data - Graph API query builder: Constructs HTTP requests to
https://graph.instagram.com/{IG_USER_ID}/mediawith filters formedia_type(IMAGE, CAROUSEL) and timestamp ranges derived from the event date - Response mapping: Transforms Instagram media objects into a canonical format matching the guest photo schema (id, caption, timestamp, media_url, source: "instagram")
The implementation follows a dormant-by-default pattern: if credentials are absent, the endpoint gracefully returns guest photos only, avoiding deployment failures or runtime errors.
Token Exchange Strategy
Instagram's Graph API requires a two-step token lifecycle:
- Short-lived token generation: Obtained via the Facebook Developer Tools Explorer with scopes
instagram_basicandpages_show_list. This token is valid for ~2 hours and used only to bootstrap the long-lived token. - Long-lived token exchange: A server-side token exchange call (made via Lambda or manual CLI) converts the short-lived token into a 60-day token using the app's
APP_IDandAPP_SECRET.
The exchange endpoint is:
GET https://graph.instagram.com/oauth/access_token?grant_type=ig_refresh_token&access_token={LONG_LIVED_TOKEN}
This returns a refreshed token with a new expiration timestamp, enabling monthly refresh cycles without user re-authentication.
Frontend Integration
The index.html file was updated to fetch and merge Instagram media into the existing guest photo gallery. The page now:
- Calls
/g/{event_id}endpoint, which returns a JSON structure with bothguest_photosandinstagram_mediaarrays - Sorts combined results by timestamp, preserving upload order across both sources
- Renders each photo with a source badge ("Guest Upload" or "Instagram @sailjada") and clickable links to Instagram when applicable
- Degrades gracefully if Instagram data is unavailable (empty array)
Infrastructure and Deployment
Lambda Function Configuration
The Lambda function shipcaptaincrew (region: us-east-1, account: 782785212866) was updated with environment variables using the AWS CLI:
aws lambda update-function-configuration \
--function-name shipcaptaincrew \
--region us-east-1 \
--environment Variables="{IG_USER_ID=VALUE,IG_ACCESS_TOKEN=VALUE}"
These values are sourced from the token exchange process described above and should be refreshed monthly before the 60-day token expires.
CloudWatch Logs and Debugging
Lambda function logs are aggregated at /aws/lambda/shipcaptaincrew in CloudWatch. Debugging the Instagram integration can be performed with:
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow
The function logs token validation results and API response codes to aid in troubleshooting connectivity or credential issues.
S3 and Static Assets
The index.html is deployed to S3 at the path corresponding to the ShipCaptainCrew tool distribution. No bucket-level changes were required; the static asset already existed and was updated in-place.
Key Decisions and Rationale
Why Dormant-by-Default?
Instagram credentials are optional in this design. If they're absent, the service continues to serve guest photos without degradation. This allows:
- Safe deployments without requiring all secrets to be in place immediately
- Easier local development and testing of the guest photo system independently
- Graceful handling if the Instagram integration needs to be disabled temporarily
Why Long-Lived Tokens Instead of User OAuth Flow?
A server-side long-lived token avoids the complexity of redirecting users through Facebook Login. Since @sailjada is a business account under the same domain, a token generated once and refreshed monthly is simpler and more reliable than a user-facing OAuth flow.
Why Not Use Instagram Basic Display?
Instagram Basic Display API is limited to recently published media and lacks filtering by timestamp. The Graph API provides richer query capabilities (date ranges, media types) required for matching photos to charter events.
What's Next
- Monthly token refresh automation: Set up an EventBridge rule to trigger a Lambda function on the 1st of each month, executing the token exchange and updating the
IG_ACCESS_TOKENenvironment variable automatically. - Hashtag filtering: Extend the query logic to filter Instagram posts by hashtags (e.g., #SailJada or event-specific tags) for better relevance.
- Media caching: Implement a DynamoDB cache layer with TTL to reduce API calls to Instagram's Graph API during high-traffic periods.
- Analytics: Track which photos receive clicks and whether Instagram-sourced media drives more engagement than guest uploads.
Verification
Test the integration by visiting https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29 (or any event date with known Instagram posts). The page should display both guest-uploaded photos and Instagram media in chronological order with source attribution.