Integrating Instagram Graph API with AWS Lambda: Building a Guest Photo Gallery with Social Media Feeds

What Was Done

We implemented Instagram Graph API integration into an existing AWS Lambda function powering the guest photo gallery at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The system now fetches and displays approved guest-uploaded charter photos alongside corresponding @sailjada Instagram posts from matching date/time windows. The integration required adding the Instagram Graph API product to the Facebook app, generating appropriate access tokens, and updating Lambda environment variables to activate dormant API code.

Architecture Overview

The guest photo gallery operates as a serverless application with this flow:

  • Frontend: Static HTML/JavaScript at /tools/shipcaptaincrew/index.html served via S3 + CloudFront
  • API Layer: AWS Lambda function shipcaptaincrew (region: us-east-1, account: 782785212866) handles routing and data aggregation
  • Guest Route: /g/{event_id} queries DynamoDB for approved photos and calls Instagram Graph API
  • Social Feed: Instagram endpoints return media from @sailjada business account within configurable time windows

Previously, the Instagram integration was present but dormant—the code checked for IG_USER_ID and IG_ACCESS_TOKEN environment variables and returned empty arrays when absent. This walkthrough activates that functionality.

Technical Details: Facebook App Configuration

Step 1: Add Instagram Graph API Product

Navigate to developers.facebook.com/apps and open the sailjada-social application. In the left sidebar near the bottom, click Add Product. From the product catalog, select Instagram Graph API (critical: not Basic Display, not Messaging—those won't grant the required scopes). This creates a new section in the sidebar labeled "Instagram Graph API."

Step 2: Connect the Instagram Business Account

Within the Instagram Graph API section, navigate to API setup with Instagram login. Click Add Instagram account and authenticate as @sailjada. The account must be a Business or Creator account linked to a Facebook Page—this is a hard requirement for Graph API media access. Verify the connection succeeds.

Technical Details: Token Generation and Exchange

Step 3: Generate Short-Lived Access Token

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

  • Select the sailjada-social app from the dropdown
  • Click Generate Access Token
  • When prompted, select the Facebook Page linked to @sailjada
  • Approve scopes: instagram_basic and pages_show_list
  • Copy the generated short-lived token (valid ~2 hours)

Step 4: Retrieve IG_USER_ID

With the short-lived token, make two sequential API calls to traverse Facebook Page → Instagram Business Account:

curl -s "https://graph.instagram.com/me?fields=id,username&access_token=YOUR_SHORT_LIVED_TOKEN"

This returns your Facebook Page ID. Then extract the Instagram business account ID:

curl -s "https://graph.instagram.com/{PAGE_ID}?fields=instagram_business_account&access_token=YOUR_SHORT_LIVED_TOKEN"

The instagram_business_account.id field in the response is your IG_USER_ID. Store this value.

Step 5: Exchange for Long-Lived Token

Short-lived tokens expire in ~2 hours. Exchange it for a long-lived token (valid 60 days) using your app credentials:

curl -s "https://graph.instagram.com/oauth/access_token?grant_type=ig_refresh_token&access_token=YOUR_SHORT_LIVED_TOKEN"

The response includes a new access_token with extended validity. This is your IG_ACCESS_TOKEN environment variable value.

Infrastructure: Lambda Environment Variables

Update the Lambda function configuration with the retrieved credentials:

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

Alternatively, update via the AWS Console: Lambda → shipcaptaincrew → Configuration → Environment Variables.

The Lambda function (file path: /tools/shipcaptaincrew/lambda_function.py) contains dormant code in the guest route handler that checks these variables. When both are present and non-empty, the handler:

  • Queries the Instagram Media endpoint for posts from @sailjada
  • Filters results by timestamp windows matching the event date
  • Merges Instagram media with DynamoDB-stored guest photos in the JSON response

Code Architecture: How It Works

In lambda_function.py, the guest route handler includes logic similar to:

def get_instagram_posts(user_id, access_token, start_time, end_time):
    if not user_id or not access_token:
        return []
    
    url = f"https://graph.instagram.com/{user_id}/media"
    params = {
        "fields": "id,caption,media_type,media_url,timestamp",
        "access_token": access_token
    }
    
    response = requests.get(url, params=params)
    posts = response.json().get("data", [])
    
    # Filter by timestamp window
    filtered = [p for p in posts 
                if start_time <= p["timestamp"] <= end_time]
    return filtered

When the guest route processes a request to /g/2026-04-29, it:

  1. Parses the event date from the URL path
  2. Queries DynamoDB for approved photos from that event
  3. Calls get_instagram_posts() with the environment variable values
  4. Merges both datasets in the JSON response
  5. The frontend renders them in a gallery view

Key Decisions

Why 60-day tokens instead of app-user tokens? Long-lived tokens require monthly refresh via a simple exchange call, but don't require the user to re-authenticate. This is simpler than storing user tokens. We can automate the refresh using an EventBridge scheduled rule if needed.

Why check environment variables rather than fetch credentials on every request? Storing tokens in Lambda environment variables (encrypted at rest by default) avoids API calls to a secrets manager for every guest request, reducing latency and cost. Since we control the infrastructure, this trade-off is acceptable.

Why use Instagram Graph API instead of Basic