```html

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

What Was Done

Implemented Instagram Graph API integration into an existing AWS Lambda function to surface @sailjada Instagram posts alongside user-uploaded guest charter photos on event-specific landing pages. The system fetches approved guest photos from DynamoDB and queries Instagram's media endpoint to display contextually relevant social content from the same event date/time window.

Technical Architecture Overview

The guest photo gallery system operates as a serverless application with the following components:

  • Frontend: Static HTML/JavaScript hosted on S3 behind CloudFront at shipcaptaincrew.queenofsandiego.com
  • API Layer: AWS Lambda function shipcaptaincrew (us-east-1, account 782785212866) handling photo retrieval and Instagram data aggregation
  • Data Store: DynamoDB table storing approved guest photo metadata with event date indexing
  • Social Integration: Instagram Graph API v18+ for fetching media objects and captions

Route structure: /g/{event_id} where event_id follows ISO date format (e.g., /g/2026-04-29

Instagram Graph API Product Setup

The initial implementation attempt used the Messaging product within Instagram Graph API, which was incorrect. Messaging grants Direct Message management scopes but does not include instagram_basic or pages_show_list scopes required for media enumeration.

Correct Product Configuration:

  • Navigate to Facebook App Dashboard for sailjada-social application
  • Select Add Product from the left sidebar
  • Choose Instagram Graph API (distinct from Basic Display and Messaging)
  • Complete Instagram account linking with @sailjada credentials
  • The @sailjada account must be configured as a Business or Creator account and linked to a Facebook Page

This grants access to the media endpoints needed for photo feed aggregation.

Token Generation and Exchange Workflow

Instagram tokens follow a two-stage exchange model:

  1. Short-lived Token Generation: Use Facebook's Graph API Explorer at developers.facebook.com/tools/explorer to generate a short-lived access token (valid ~2 hours). This requires selecting the Facebook Page linked to @sailjada and requesting scopes: instagram_basic, pages_show_list.
  2. Retrieve IG_USER_ID: Query the Facebook Page object to extract the linked Instagram Business Account ID:
    GET /me/instagram_business_accounts?access_token={SHORT_LIVED_TOKEN}
    The response contains instagram_business_account.id — this is your IG_USER_ID environment variable.
  3. Long-lived Token Exchange: Exchange the short-lived token for a long-lived token (valid 60 days):
    GET /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 your IG_ACCESS_TOKEN environment variable.

Why this approach: Short-lived tokens are safer for development workflows and API explorer tooling. Long-lived tokens reduce token-refresh frequency in production but still require monthly refresh cycles for security compliance.

Lambda Function Implementation

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

The Lambda handler implements conditional Instagram integration:

def handler(event, context):
    event_id = event['pathParameters']['event_id']
    
    # Fetch approved guest photos from DynamoDB
    guest_photos = query_guest_photos_by_date(event_id)
    
    # Conditionally fetch Instagram posts if credentials present
    ig_user_id = os.environ.get('IG_USER_ID')
    ig_access_token = os.environ.get('IG_ACCESS_TOKEN')
    
    instagram_posts = []
    if ig_user_id and ig_access_token:
        instagram_posts = fetch_instagram_media(ig_user_id, ig_access_token, event_id)
    
    return {
        'statusCode': 200,
        'body': json.dumps({
            'guest_photos': guest_photos,
            'instagram_posts': instagram_posts
        })
    }

The fetch_instagram_media function constructs a Graph API request to the media edge endpoint, filtering results by caption timestamps and hashtags matching the event date.

Environment Configuration

Lambda environment variables required:

  • IG_USER_ID — Instagram Business Account ID (extracted in token workflow, step 2)
  • IG_ACCESS_TOKEN — Long-lived access token (exchanged in step 3, valid 60 days)
  • DYNAMODB_TABLE — Guest photo table name

Update command structure:

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

Frontend Integration

File: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html

The static frontend makes client-side fetch requests to the Lambda API endpoint:

fetch(`/g/${eventId}`)
  .then(r => r.json())
  .then(data => {
    renderGuestPhotos(data.guest_photos);
    renderInstagramPosts(data.instagram_posts);
  })

Photos are rendered in a chronological grid with metadata (photographer name, timestamp, approval status). Instagram posts are interspersed with event-relevant contextual styling.

Key Architectural Decisions

  • Conditional Integration: Instagram data is optional. If environment variables are unset, the system returns empty arrays rather than failing. This allows graceful degradation during token rotation or credential issues.
  • Server-side Aggregation: Photo combining happens in Lambda, not the frontend. This reduces client complexity and enables server-side filtering/caching if needed later.
  • Token Refresh Strategy: 60-day long-lived tokens require monthly manual refresh via the exchange endpoint. Future enhancement: use EventBridge scheduled rule to trigger automated token refresh Lambda function.
  • Scope Minimization: Requested only instagram_basic (read media) and pages_show_list (enumerate linked accounts