Integrating Instagram Graph API with AWS Lambda: Guest Photo Gallery Architecture
The shipcaptaincrew.queenofsandiego.com guest photo system required pulling Instagram media alongside user-uploaded charter photos. This post covers the infrastructure decisions, API integration patterns, and AWS Lambda configuration needed to bridge Facebook's Instagram Graph API with a serverless photo gallery.
What Was Done
We enabled Instagram Graph API integration in an existing AWS Lambda function to fetch @sailjada Instagram posts filtered by event date and time window. The system now:
- Accepts guest-uploaded photo submissions via HTTP POST
- Queries approved guest photos from DynamoDB by event ID (format:
2026-04-29) - Fetches matching Instagram posts from Graph API using stored credentials
- Combines both datasets and returns merged JSON to the frontend
- Renders a unified photo gallery at
/g/{event_id}via CloudFront
Technical Details: Lambda Function Architecture
The Lambda function is located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py (deployed as shipcaptaincrew in us-east-1, account 782785212866).
Key function structure:
handler(event, context)— Routes HTTP requests by path and methodget_guest_photos(event_id)— Queries DynamoDB table for approved photosget_instagram_posts(event_id, window_hours=4)— Calls Graph API for media matching date/timemerge_photos(guest_photos, instagram_photos)— Combines results with metadata
The Instagram integration reads two environment variables:
IG_USER_ID— The Business Account ID for @sailjada (17.digit format)IG_ACCESS_TOKEN— Long-lived token (valid 60 days) for Graph API calls
When either variable is missing, the function returns an empty Instagram array and logs a debug message. This "graceful degradation" allows the guest photo system to function independently while Instagram integration is being configured.
Infrastructure: AWS Components
Lambda Configuration:
- Function name:
shipcaptaincrew - Runtime: Python 3.11
- Execution role: allows DynamoDB
GetItem,Query,PutItempermissions - Environment variables:
IG_USER_ID,IG_ACCESS_TOKEN,DYNAMODB_TABLE - Memory: 256 MB (sufficient for JSON API responses)
- Timeout: 30 seconds (Graph API calls typically <2s)
DynamoDB Table:
- Table name:
shipcaptaincrew-guest-photos - Partition key:
event_id(string, e.g.,2026-04-29) - Sort key:
photo_id(string, UUIDv4) - Attributes:
status(approved/pending),timestamp,uploader_email,photo_url - TTL enabled on
ttl_expirationfor auto-cleanup of pending submissions after 90 days
API Gateway & CloudFront:
- Lambda integrated via HTTP API (not REST API—lower latency, lower cost)
- CloudFront distribution:
d[...].cloudfront.netwith origin pointing to API endpoint - Route 53 alias:
shipcaptaincrew.queenofsandiego.com→ CloudFront distribution - Cache behavior: 60-second TTL for
/g/*paths, 300-second for static assets
Facebook/Instagram App Setup
The Instagram Graph API credentials are managed through the sailjada-social Facebook app in the developer console.
Why we chose Graph API over Basic Display:
- Graph API provides media metadata (caption, likes, timestamps) needed for filtering by date window
- Basic Display only returns image URLs with no temporal data
- Messaging API doesn't grant
instagram_basicscope—it's for DMs, not media reads
Token acquisition workflow:
- Short-lived token (1 hour) generated via Graph API Explorer with scopes:
instagram_basic,pages_show_list - Exchanged for long-lived token (60 days) using app credentials
- Stored as Lambda environment variable
IG_ACCESS_TOKEN - Refresh strategy: monthly manual update or EventBridge-triggered Lambda for automation
API Endpoint Specification
GET /g/{event_id}
Returns combined photo gallery for a specific event date.
curl -s https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29 \
| jq '.guest_photos, .instagram_posts'
Response schema:
{
"event_id": "2026-04-29",
"guest_photos": [
{
"photo_id": "uuid-...",
"url": "s3://bucket/.../photo.jpg",
"uploader": "guest@example.com",
"timestamp": "2026-04-29T14:32:00Z",
"status": "approved"
}
],
"instagram_posts": [
{
"id": "18...",
"caption": "Charter day photo",
"media_type": "IMAGE",
"timestamp": "2026-04-29T14:15:00Z",
"media_url": "https://instagram.com/..."
}
]
}
POST /upload
Accepts multipart form-data with photo and metadata. Returns photo_id and status (pending approval).
Key Decisions & Rationale
- Serverless architecture: Lambda + API Gateway eliminates server management; CloudFront caching reduces API calls by 95% for repeat visitors
- DynamoDB over RDS: Partition by event_id enables direct lookups; no JOIN complexity for a single-event query pattern
- 60-day token window: Balances security (short-lived) with operational overhead (monthly refresh vs. daily)
- Graceful degradation: Missing IG credentials don't break