```html

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

What Was Done

We integrated the Instagram Graph API into an existing AWS Lambda function to automatically fetch and display @sailjada Instagram posts alongside user-uploaded charter photos on a guest gallery page. The system lives at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29) and correlates Instagram media with guest uploads based on shared timestamps.

Architecture Overview

The guest photo system is a three-layer architecture:

  • Frontend: Static HTML/JavaScript at /tools/shipcaptaincrew/index.html served via S3 + CloudFront
  • Backend: AWS Lambda function (shipcaptaincrew, us-east-1) that handles both guest photo approval workflows and Instagram media fetching
  • Data sources: DynamoDB for guest uploads, Instagram Graph API for media metadata

The Lambda function was dormant for Instagram integration until token provisioning was complete. Without valid IG_USER_ID and IG_ACCESS_TOKEN environment variables, the function safely returns an empty array rather than failing.

Technical Details: Instagram Graph API Integration

App Configuration

The integration required adding the correct product to the existing sailjada-social Facebook app (ID: 1688884572116630). The critical first step was selecting Instagram Graph API as the product type, not Basic Display or Messaging (the latter was previously added but doesn't grant the required instagram_basic scope for reading media).

Why this matters: The Messaging product is designed for inbox management, not media reads. It won't expose the scopes needed to list media or fetch captions. Instagram Graph API is the correct product for fetching public business account media.

Token Generation Flow

The token provisioning follows a two-step exchange pattern:

  1. Short-lived token generation (valid 1 hour): Generated via the Graph API Explorer at developers.facebook.com/tools/explorer, selecting the Facebook Page linked to @sailjada. Required scopes: instagram_basic and pages_show_list.
  2. Retrieve IG_USER_ID: Using the short-lived token, query the Facebook Page's connected Instagram business account:
    GET /v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}
    The response contains the Instagram account ID nested under instagram_business_account.id. This becomes IG_USER_ID.
  3. Exchange for long-lived token (valid 60 days):
    GET /v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&access_token={SHORT_LIVED_TOKEN}
    The returned access_token is the long-lived IG_ACCESS_TOKEN.

Lambda Environment Configuration

The tokens are stored as Lambda environment variables:

  • IG_USER_ID – The numeric Instagram business account ID
  • IG_ACCESS_TOKEN – The 60-day long-lived access token

Updated via AWS CLI:

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

Lambda Function Changes

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

The Lambda handler includes a dedicated Instagram fetching routine that:

  1. Checks for required env vars; returns [] if missing (fail-safe behavior)
  2. Queries the Instagram Graph API endpoint:
    GET /v18.0/{IG_USER_ID}/media?fields=id,caption,media_type,media_url,timestamp&access_token={IG_ACCESS_TOKEN}
  3. Filters results by a configurable time window (e.g., event date ± 12 hours)
  4. Returns photo objects with caption, URL, and timestamp for frontend rendering

Design decision: We kept Instagram and guest photo fetching in the same handler but in separate code paths. This allows independent scaling and debugging—if the Instagram API is slow, guest photo queries still respond quickly.

Frontend Integration

File: /tools/shipcaptaincrew/index.html

The guest page makes a single API call to the Lambda function's /g/{event_id} route, which returns a JSON payload:

{
  "guest_photos": [...],
  "instagram_photos": [...],
  "event_date": "2026-04-29"
}

The frontend loops through both arrays and renders them interleaved by timestamp, allowing a visual chronology of the event as captured by both guests and the official @sailjada account.

Infrastructure & Deployment

  • Lambda function: shipcaptaincrew in us-east-1, account 782785212866
  • Custom domain: shipcaptaincrew.queenofsandiego.com via Route53 CNAME pointing to API Gateway
  • CloudWatch logs: /aws/lambda/shipcaptaincrew for debugging token exchange and API errors
  • DynamoDB table: Stores guest photo metadata and approval status (existing table)

Key Decisions & Trade-offs

  • 60-day token refresh: Rather than using app-only tokens (which Instagram doesn't support for business accounts), we exchange tokens monthly using an EventBridge trigger. This avoids hardcoding user credentials while maintaining media access.
  • Time-window filtering: We filter Instagram media by event date rather than hashtag matching. Hashtags are unreliable and require explicit scanning; timestamp correlation is deterministic.
  • Separate API product: Messaging was initially added; we corrected it to Instagram Graph API. This teaches an important lesson: Facebook's product ecosystem is granular. Each use case requires the right product; permissions don't transfer.
  • Fail-safe for missing tokens: Rather than throwing errors, missing tokens return empty arrays. This lets the guest page work with only approved photos if Instagram isn't yet configured.