```html

Integrating Instagram Graph API with AWS Lambda: Enabling Photo Feed Aggregation for Guest Event Pages

What Was Done

We enabled Instagram media integration for the Ship Captain Crew guest photo gallery system by configuring the Instagram Graph API within our existing Facebook app (sailjada-social) and updating the AWS Lambda function to consume authenticated API calls. The guest event pages at shipcaptaincrew.queenofsandiego.com/g/{event_id} now have the plumbing to display @sailjada Instagram posts alongside guest-uploaded charter photos, sorted by event date and time window.

The Lambda function at /aws/lambda/shipcaptaincrew (region: us-east-1, account: 782785212866) previously had Instagram integration code in a dormant state—it returned empty arrays when environment variables were absent. This walkthrough documents the infrastructure and configuration decisions to activate that feature.

Technical Architecture: Why This Approach

Three-layer integration pattern:

  • Facebook App Tier: The sailjada-social app serves as the OAuth broker. We added the Instagram Graph API product (distinct from the Messaging product already present) to grant media-read permissions.
  • Token Exchange Tier: Short-lived tokens (1-hour validity) are exchanged for long-lived tokens (60-day validity) using the app's credentials, stored securely as Lambda environment variables.
  • Lambda Invocation Tier: The guest page handler reads IG_USER_ID and IG_ACCESS_TOKEN from environment, queries the Graph API directly, filters results by event date, and merges with guest-uploaded photos before returning JSON to the frontend.

Why not Basic Display API? Basic Display is simpler but limited to non-business accounts and doesn't support filtering by time window or hashtags. The Graph API requires a Business or Creator account (which @sailjada is) and a Facebook Page connection, but provides the structured queries and metadata we need for reliable aggregation.

Facebook App Configuration

Our app dashboard at developers.facebook.com/apps under sailjada-social required two distinct product additions:

  • Existing: Messenger product (for DM handling on other features)
  • New: Instagram Graph API product—added via the "Add Product" button in the left sidebar

The Instagram Graph API product setup requires linking the @sailjada Instagram account to a Facebook Page. This was done via the API setup flow: Instagram Graph APIAPI setup with Instagram loginAdd Instagram account, logging in as @sailjada.

Key decision: We confirmed @sailjada is classified as a Creator account (not Personal), which unlocks the instagram_business_account field in Graph API responses—essential for extracting the IG_USER_ID.

Token Generation and Storage

Step 1: Short-lived token generation

Using the Graph API Explorer at developers.facebook.com/tools/explorer:

# Select app: sailjada-social
# Generate Access Token with scopes: instagram_basic, pages_show_list
# Scopes grant permission to read media and list connected pages

Step 2: Extract IG_USER_ID

The short-lived token is exchanged for page information, then dereferenced to the Instagram business account:

# Pseudo-command (credentials omitted):
curl -s "https://graph.facebook.com/v18.0/me/accounts" \
  -d "fields=instagram_business_account" \
  -d "access_token=SHORT_LIVED_TOKEN"

# Response contains: instagram_business_account.id = IG_USER_ID

Step 3: Long-lived token exchange

The 1-hour token is exchanged for a 60-day token using the app's secret:

curl -s "https://graph.facebook.com/oauth/access_token" \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=APP_ID" \
  -d "client_secret=APP_SECRET" \
  -d "fb_exchange_token=SHORT_LIVED_TOKEN"

# Response: access_token (valid 60 days) = IG_ACCESS_TOKEN

Storage decision: Both IG_USER_ID and IG_ACCESS_TOKEN are stored as Lambda environment variables. They are encrypted at rest by AWS Lambda's default KMS encryption. No rotation is automatic; a manual refresh is required before the 60-day window closes (discussed below).

Lambda Function Updates

The Lambda handler at sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py contains the guest page route. The function already had conditional Instagram logic:

def get_instagram_photos(user_id, access_token, event_date):
    """Returns empty array if env vars missing, else queries Graph API."""
    if not user_id or not access_token:
        return []
    # Query logic here

Environment variables set via AWS Lambda console or CLI:

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

The handler now filters Instagram posts by event date (matching the event_id parameter in the URL, e.g., 2026-04-29) and merges them with guest photos in chronological order before returning the JSON response to the frontend at /g/{event_id}.

Infrastructure Considerations

  • CloudFront caching: The guest page at shipcaptaincrew.queenofsandiego.com is served through CloudFront (distribution ID not listed here for security). Guest photo API responses include Instagram data, so cache headers were reviewed to ensure dynamic content isn't stale. The Lambda function returns appropriate Cache-Control headers based on photo freshness.
  • Lambda IAM role: The existing execution role for shipcaptaincrew does not require additional permissions; Instagram Graph API calls are authenticated via the token in environment variables, not IAM roles.
  • S3 asset hosting: Guest-uploaded photos are stored in the S3 bucket (path not specified here). Instagram media is fetched directly from Instagram's CDN via the Graph API response, avoiding duplication.

Token Refresh Strategy

Why manual refresh? The 60-day token expiration is longer than typical development cycles. For now, a manual refresh process before expiration is acceptable. Future enhancement: EventBridge scheduled rule invoking a Lambda function monthly to exchange the token and update the environment variable.

Refresh procedure: Run the short-lived and long-lived exchange steps again, then update Lambda configuration with the new IG_ACCESS_TOKEN. No code changes required.

Verification and Testing