```html

Integrating Instagram Graph API into a Lambda-based Guest Photo Gallery: Architecture and Implementation

What Was Done

We integrated the Instagram Graph API into an existing AWS Lambda function to display @sailjada Instagram posts alongside guest-uploaded charter photos on a dynamic guest page system. The guest photo gallery lives at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29) and now fetches Instagram media from the same day/time window as charter events, merging both data sources into a single curated feed.

The implementation required three main components: (1) configuring the Instagram Graph API product in the Facebook App, (2) generating and managing long-lived access tokens, and (3) updating the Lambda function to call the Graph API and return Instagram media alongside approved guest photos.

Technical Details: Facebook App Configuration

The existing Facebook app sailjada-social (App ID: 1688884572116630, account 782785212866) was initially configured with the Messaging product, which is designed for direct message handling and does not grant the instagram_basic scope required to read media. This was the critical blocker.

Step 1: Add the correct product

  • Navigate to developers.facebook.com/apps and open sailjada-social
  • In the left sidebar, locate and click Add Product
  • Search for Instagram and select Instagram Graph API (not Basic Display, not Messaging)
  • The product now appears in the sidebar as a distinct integration point

Why this matters: The Instagram Graph API product is the only path to request instagram_basic scope, which permits reading a Business/Creator account's media, captions, timestamps, and media URLs. The Messaging product is for inbound DM handling and will never grant this scope, making it incorrect for our use case.

Step 2: Connect the Instagram business account

  • Inside Instagram Graph API settings, locate API setup with Instagram login
  • Click Add Instagram account and authenticate as @sailjada
  • The account must be a Business or Creator account (not a personal account) to access Graph API endpoints

Token Generation and Exchange Flow

Instagram Graph API tokens follow a two-tier system: short-lived tokens (1 hour) are generated manually for testing and development, while long-lived tokens (60 days) are suitable for production Lambda functions. We implemented both flows to maximize security and operational flexibility.

Generating a short-lived token:

  1. Open the Graph API Explorer at developers.facebook.com/tools/explorer
  2. In the top dropdown, select app sailjada-social
  3. Click Generate Access Token, then select the Facebook Page linked to @sailjada
  4. Ensure the requested scopes include instagram_basic and pages_show_list
  5. Copy the generated token and store it temporarily

Extracting IG_USER_ID:

With a short-lived token, make a call to the Pages API to locate the Instagram business account ID:

curl -s "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}" | jq .

The response contains instagram_business_account.id, which becomes your IG_USER_ID environment variable.

Exchanging for a long-lived token:

To generate a 60-day token suitable for Lambda, exchange the short-lived token using the app credentials:

curl -s "https://graph.instagram.com/access_token?grant_type=ig_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&access_token={SHORT_LIVED_TOKEN}" | jq .

The returned access_token is your IG_ACCESS_TOKEN, valid for 60 days. Before expiration, the same exchange endpoint can be called again to refresh the token without re-authentication, making it suitable for scheduled EventBridge triggers if needed.

Infrastructure and Lambda Updates

The Lambda function shipcaptaincrew (us-east-1, 782785212866) required environment variable configuration and code changes to integrate Instagram media fetching.

Environment variables:

  • IG_USER_ID — The Instagram business account ID extracted from the Pages API call
  • IG_ACCESS_TOKEN — The long-lived access token (60-day validity)

Update these using the AWS Lambda console or CLI:

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

Code changes in lambda_function.py:

The Lambda handler, located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py, was modified to conditionally fetch Instagram media when both environment variables are present. When either is missing, the function returns an empty array, preserving backward compatibility.

The Instagram fetch logic queries the Media endpoint:

GET https://graph.instagram.com/v18.0/{IG_USER_ID}/media?fields=id,caption,timestamp,media_type,media_url,permalink&access_token={IG_ACCESS_TOKEN}

Results are filtered by timestamp to match the event date window (e.g., photos posted on 2026-04-29) and merged with approved guest photos before being returned to the frontend.

Frontend Integration

The index.html file at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html was updated to handle Instagram media in the gallery display. The API response now includes both guest photos and Instagram posts, each with metadata (uploader, timestamp, source). The frontend renders them in chronological order with visual indicators showing the source (Guest Upload vs. @sailjada Instagram).

Key Architectural Decisions

  • Dormant-by-default pattern: Instagram integration is conditional on environment variables, allowing the Lambda to function independently if tokens are unavailable or revoked.
  • 60-day token lifespan: Long-lived tokens reduce the operational burden of daily token refresh while remaining practical for scheduled refresh jobs if needed in the future.
  • No Lambda Layer for Instagram SDK: We used native boto3 and Python requests library to avoid dependency bloat; the Instagram Graph API is straightforward HTTP.
  • Timestamp-based filtering: Rather than storing event metadata separately, we filter Instagram results by the same day as the