```html

Integrating Instagram Graph API with AWS Lambda: Adding Social Media Context to Guest Photo Pages

What Was Done

We enhanced the guest photo gallery system at 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 the Instagram Graph API product, obtaining proper authentication tokens, and updating the Lambda function environment variables to activate the dormant Instagram integration code.

Technical Details

Current Architecture

The guest photo page system consists of three core components:

  • Frontend: /tools/shipcaptaincrew/index.html — static HTML/JavaScript served via CloudFront that renders guest photos and Instagram media in a unified gallery
  • Lambda Function: shipcaptaincrew (us-east-1, account 782785212866) — handles API requests for guest photos and Instagram media, with environment variables controlling feature activation
  • Data Layer: Guest-uploaded photos stored in DynamoDB with approval workflow; Instagram data fetched on-demand via Graph API

The Lambda function contained placeholder logic for Instagram integration that returned empty arrays when the required environment variables (IG_USER_ID, IG_ACCESS_TOKEN) were undefined. This allowed safe deployment without breaking the guest photo feature.

Facebook Developer App Configuration

The first critical step was adding the correct product to the existing sailjada-social app in the Facebook Developer Console. The app previously had only the Messaging product configured, which grants permissions for Direct Messages but not media access. Instagram Graph API requires a separate product setup with explicit scopes.

Configuration steps executed:

  • Navigated to developers.facebook.com/apps → sailjada-social → Add Product
  • Selected Instagram Graph API product (distinct from Instagram Basic Display or Messaging)
  • This product was added to the left sidebar alongside existing products
  • The @sailjada account was then registered as an Instagram Business/Creator account connected to the Facebook Page linked to the app

Why this approach: The Instagram Graph API product automatically grants the necessary OAuth scopes (instagram_basic, pages_show_list) needed to read media metadata. Using the Messaging product would have failed scope validation when requesting media endpoints.

Token Generation and Exchange

Obtaining long-lived credentials required a two-step token exchange process:

Step 1: Short-lived token generation

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

  • Selected app: sailjada-social
  • Generated access token with scopes: instagram_basic, pages_show_list
  • This token has a ~2 hour lifespan and is used to access the Graph API directly

Step 2: Retrieving IG_USER_ID

Two sequential Graph API calls extract the Instagram business account ID:

# First call: Get the Facebook Page ID and linked Instagram account
curl -s "https://graph.facebook.com/v18.0/me/accounts?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN"

# Response contains: "instagram_business_account": { "id": "17841400..." }
# This ID becomes IG_USER_ID

Step 3: Token longevity exchange

The short-lived token was exchanged for a 60-day long-lived token using the client credentials:

curl -s "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN"

The returned access_token becomes IG_ACCESS_TOKEN. This extended validity allows querying Instagram media without daily token refresh overhead.

Lambda Environment Variable Updates

The Lambda function configuration was updated with two environment variables:

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

These variables activate the existing Instagram fetch logic in lambda_function.py. The handler checks for their presence before attempting Graph API calls, preventing errors in environments where Instagram integration isn't configured.

Code Changes

The Lambda function modifications in /tools/shipcaptaincrew/lambda_function.py were minimal — primarily fixing the dormant Instagram integration code path:

  • Added environment variable validation at handler initialization
  • Implemented retry logic for Graph API calls (500 errors, rate limits)
  • Ensured photo timestamp filtering correctly matches guest photos to Instagram posts within ±6 hour window

The index.html frontend changes involved:

  • Adding a loading state while fetching Instagram media
  • Displaying Instagram post metadata (caption, engagement metrics, media URLs) alongside guest photos
  • Implementing client-side filtering to show only media from the event date

Infrastructure Decisions

Token Refresh Strategy

Rather than implementing immediate automated refresh, we established a 60-day rotation cycle. Monthly alerts (via SNS or email) remind the ops team to regenerate the long-lived token before expiration. This avoids the complexity of Lambda-to-Graph-API token refresh logic while remaining pragmatic for a low-volume use case. If traffic patterns change, EventBridge can automate monthly refresh by invoking a dedicated Lambda function.

Scope Minimization

The token was granted only instagram_basic (read media) and pages_show_list (enumerate pages). No Direct Message, analytics, or user management scopes were requested, limiting blast radius if credentials are ever exposed.

Error Handling

Instagram API failures (invalid tokens, rate limits, network errors) are caught and logged without blocking guest photo retrieval. The page degrades gracefully, showing approved guest photos with Instagram data as a secondary enhancement.

Verification

The integration was verified by checking the guest photo page at shipcaptaincrew.queenofsandiego.com/g/2026-04-29. Lambda logs in CloudWatch (namespace: /aws/lambda/shipcaptaincrew) confirm successful Graph API calls and token validation.

What's Next

  • Monitor CloudWatch logs for any authentication drift or rate limiting
  • Set a calendar reminder for 60-day token refresh using the same exchange flow
  • If usage grows, consider caching Instagram media in DynamoDB to reduce API calls
  • Evaluate EventBridge automation for token refresh once operational confidence is established
```