```html

Integrating Instagram Graph API with AWS Lambda: Connecting Guest Photo Events to Social Media

Overview

The Ship Captain Crew guest photo system needed to aggregate user-uploaded charter photos alongside official @sailjada Instagram posts from matching event dates. This required adding Instagram Graph API integration to an existing AWS Lambda function deployed at shipcaptaincrew.queenofsandiego.com. The challenge: properly configuring Facebook App permissions, obtaining long-lived access tokens, and maintaining them across Lambda function lifecycles.

What Was Done

We completed the Instagram Graph API integration pipeline for the guest photo system by:

  • Added Instagram Graph API product to the existing sailjada-social Facebook App (previously only had Messaging API configured)
  • Connected the @sailjada Instagram Business account and obtained its corresponding IG_USER_ID
  • Generated and exchanged temporary access tokens for a long-lived token (60-day validity)
  • Updated Lambda environment variables IG_USER_ID and IG_ACCESS_TOKEN in the shipcaptaincrew function
  • Modified the Lambda handler to conditionally query Instagram media when tokens are available

Technical Details: Why the Messaging API Wasn't Enough

Initial investigation found that the sailjada-social app only had the Instagram Messaging API configured. While this grants permissions for Direct Messages, it does not include the instagram_basic scope required to read media. The Graph API product needed to be added separately with explicit permissions for media access.

Why this matters: Facebook App permissions are granular. A single product (Messaging, Basic Display, Graph API) grants specific scopes. The Messaging API is designed for chatbots; it never grants media read permissions. The Instagram Graph API product is required for reading posts, captions, and media data.

Configuration Steps and Infrastructure Changes

Step 1: Add Instagram Graph API Product

  1. Navigate to developers.facebook.com/apps
  2. Select app sailjada-social
  3. In the left sidebar, click Add Product
  4. Search for "Instagram" and select Instagram Graph API (not Basic Display, not Messaging)
  5. Complete setup — this configures the Graph API permission model for the app

Step 2: Connect the Instagram Business Account

The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Page. This is a prerequisite for Graph API access.

Inside the app dashboard, under Instagram Graph API → API Setup:

  • Click Add Instagram account
  • Authenticate as @sailjada
  • Authorize the sailjada-social app to access the account

Step 3: Generate Short-Lived Token

Using the Facebook Graph API Explorer:

# Navigate to developers.facebook.com/tools/explorer
# 1. Select app: sailjada-social
# 2. Click "Generate Access Token"
# 3. Select the Facebook Page linked to @sailjada
# 4. Ensure scopes include: instagram_basic, pages_show_list
# 5. Copy the short-lived token (valid ~2 hours)

Step 4: Retrieve IG_USER_ID

The Instagram User ID is nested inside the Facebook Page's instagram_business_account field. Two API calls extract this:

# First: Get the Page ID (if not already known)
curl -s "https://graph.facebook.com/v18.0/me?access_token=TOKEN" | jq '.id'

# Second: Get the Instagram Business Account ID
curl -s "https://graph.facebook.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token=TOKEN" \
  | jq '.instagram_business_account.id'

# Result: This ID is your IG_USER_ID (e.g., "17841400000000000")

Step 5: Exchange for Long-Lived Token

Short-lived tokens expire in ~2 hours. The Graph API provides an exchange endpoint to obtain a 60-day token:

curl -s "https://graph.instagram.com/access_token" \
  -d "grant_type=ig_refresh_token" \
  -d "access_token=TOKEN" \
  | jq '.access_token'

# The returned access_token is your IG_ACCESS_TOKEN (60-day validity)

Why 60 days and not permanent? Instagram intentionally limits long-lived tokens to encourage refresh workflows. This prevents leaked tokens from remaining valid indefinitely. A 60-day window requires quarterly or monthly refresh cycles.

Step 6: Update Lambda Environment Variables

The shipcaptaincrew Lambda function in us-east-1 (account 782785212866) stores credentials as environment variables:

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

File affected: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py

The handler checks for these variables at runtime:

ig_user_id = os.environ.get('IG_USER_ID')
ig_token = os.environ.get('IG_ACCESS_TOKEN')

if ig_user_id and ig_token:
    # Query Instagram media for the event date window
    ig_posts = fetch_instagram_posts(ig_user_id, ig_token, event_date)
else:
    # Return empty array; integration inactive
    ig_posts = []

Why This Architecture?

  • Environment variables for secrets: Lambda's built-in encryption at rest protects credentials without adding a separate secrets manager. For production, AWS Secrets Manager would be ideal, but env vars are sufficient for this non-public endpoint.
  • Conditional logic in handler: The function degrades gracefully if tokens are missing. Guest photos display regardless; Instagram content is supplementary.
  • 60-day refresh cycle: Quarterly refreshes via manual command or EventBridge automation prevent token expiry without rotating credentials across the entire stack.
  • Graph API over Basic Display: Graph API provides richer metadata (captions, timestamps, media URLs) needed to cross-reference Instagram posts with guest event dates.

Testing and Verification

After updating Lambda configuration, verify the integration:

# Check CloudWatch logs for the shipcaptaincrew function
aws logs tail /aws/lambda/shipcaptaincrew --region us-east-1 --follow

# Test the guest page endpoint
curl -si "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29"

The response should include both approved guest photos and