```html

Integrating Instagram Graph API with AWS Lambda: Guest Photo Gallery Architecture

The Ship Captain Crew guest photo system needed to display approved guest-uploaded charter photos alongside live Instagram posts from @sailjada. This required bridging Facebook's Instagram Graph API with an AWS Lambda function serving a static site hosted on CloudFront. This post details the architectural decisions, infrastructure setup, and integration patterns used to make this work.

What Was Done

We enabled Instagram Graph API integration in the Lambda function that powers shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29

Technical Details: The Integration Flow

Lambda Function Architecture

The Lambda function is located at:

  • /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py
  • AWS Region: us-east-1
  • Account: 782785212866
  • Function Name: shipcaptaincrew

The handler function accepts requests to the /g/ endpoint and queries a DynamoDB table for approved guest photos matching the event date. When IG_USER_ID and IG_ACCESS_TOKEN environment variables are present, the function makes authenticated requests to the Instagram Graph API to fetch media posted by the business account during a configurable time window around the event date.

Instagram Graph API Product Configuration

The critical first step is adding the correct product to the Facebook app. The app sailjada-social had a Messaging product configured initially, which grants DM-related scopes but cannot authorize media reading. The solution required:

  1. Navigate to developers.facebook.com/apps → Select sailjada-social
  2. Click Add Product in the left sidebar
  3. Select Instagram Graph API (not Basic Display, not Messaging)
  4. Complete API setup by linking @sailjada as the Instagram business account

This grants access to the instagram_basic and pages_show_list scopes needed for reading media metadata.

Token Generation Strategy

Instagram's token lifecycle involves two types: short-lived (1 hour) and long-lived (60 days). The workflow is:

  1. Generate short-lived token via Graph API Explorer at developers.facebook.com/tools/explorer
    • Select app: sailjada-social
    • Select the Facebook Page linked to @sailjada
    • Request scopes: instagram_basic,pages_show_list
  2. Exchange for long-lived token via Graph API call:
    GET /oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&access_token={SHORT_LIVED_TOKEN}
  3. Retrieve IG_USER_ID by calling the Graph API endpoint:
    GET /{PAGE_ID}?fields=instagram_business_account&access_token={LONG_LIVED_TOKEN}
    The response includes instagram_business_account.id, which is the IG_USER_ID.

Infrastructure Changes

Lambda Environment Variables

Two new environment variables were added to the shipcaptaincrew Lambda function via aws lambda update-function-configuration:

  • IG_USER_ID: The Instagram Business Account ID (numeric string)
  • IG_ACCESS_TOKEN: Long-lived access token (60-day validity)

Example update command structure (with values redacted):

aws lambda update-function-configuration \
  --function-name shipcaptaincrew \
  --region us-east-1 \
  --environment Variables="{IG_USER_ID=17841400xxxxxx,IG_ACCESS_TOKEN=IGQVJxxxxxxxxxxxxxWZA}"

Frontend Changes

The index.html file at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html was updated to:

  • Accept the event ID from the URL path /g/{event_id}
  • Make a fetch request to the Lambda API endpoint with the event ID as a query parameter
  • Render both guest photos and Instagram media in chronological order
  • Display Instagram metadata (caption, engagement) alongside photos

The Lambda API returns a JSON object with two arrays: guest_photos and instagram_posts, merged and sorted by timestamp in the browser.

Key Architectural Decisions

Why Long-Lived Tokens Instead of Refresh Tokens

Instagram's OAuth2 flow provides 60-day long-lived tokens without explicit refresh mechanisms built into the library. For a low-traffic guest gallery, refreshing manually every 60 days via a scheduled CloudWatch Event is simpler than implementing a token refresh service. A Lambda function triggered by EventBridge could automate this, but the current approach reduces operational overhead.

Why Merge on the Client Side

The Lambda function returns separate arrays for guest photos and Instagram posts. Merging happens in the browser JavaScript rather than in Lambda because:

  • Decoupling: Guest photo queries are DynamoDB-backed; Instagram queries are API-backed. Merging in Lambda would create tight coupling.
  • Caching: Guest photos can be cached longer; Instagram posts may need fresher data. Separate arrays allow independent cache strategies.
  • Resilience: If Instagram API is slow or unavailable, guest photos still render; merging on client ensures partial failures don't block the whole response.

Why Dormant Code Over Feature Flags

The Instagram integration was originally disabled by checking for missing environment variables rather than using feature flags. This keeps the code simple for a single-purpose Lambda and avoids adding complexity around flag management. When the tokens are available, Instagram functionality activates automatically.

What's Next

  • Token Refresh Automation: Create an EventBridge rule to refresh the long-lived token every 50 days, updating the Lambda environment variable automatically.