```html

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

What Was Done

We implemented Instagram Graph API integration into the shipcaptaincrew Lambda function to automatically populate a guest photo gallery page (/g/{event_id}) with both user-uploaded charter photos and matching @sailjada Instagram posts from the same event date/time window. This required connecting the existing Lambda function to Meta's Instagram Graph API, establishing proper authentication flows, and managing long-lived access tokens with automated refresh strategies.

Architecture Overview

The system operates as a three-tier integration:

  • Client Layer: Static HTML at /tools/shipcaptaincrew/index.html (served via CloudFront) with JavaScript that calls the Lambda API endpoint
  • API Layer: Lambda function (shipcaptaincrew, region: us-east-1, account: 782785212866) that handles guest photo retrieval and Instagram media queries
  • Data Layer: DynamoDB table for approved guest photos; Instagram Graph API for media metadata

The Lambda function previously had dormant Instagram integration code that returned empty arrays when environment variables IG_USER_ID and IG_ACCESS_TOKEN were missing. Our work activated this integration by properly provisioning these credentials.

Technical Implementation Details

Meta App Configuration

The first critical decision was which Meta product to enable. The existing app sailjada-social had the "Manage Messaging" use case added, which grants DM-related permissions but does not include the instagram_basic scope needed to read media metadata. We added the Instagram Graph API product (distinct from Basic Display or Messaging) to the app configuration.

Why this matters: Meta's product system grants scopes based on use case. Messaging products don't include media read permissions. Instagram Graph API is the correct product tier for reading business account media, insights, and media children.

Account Linking and Token Generation

The process requires several sequential steps:

  1. Connect @sailjada as a Business/Creator account to the app via Instagram Graph API settings (the account must be linked to a Facebook Page that the app can access)
  2. Generate a short-lived access token using the Facebook Graph API Explorer:
    • Visit developers.facebook.com/tools/explorer
    • Select the sailjada-social app
    • Generate token for the Facebook Page linked to @sailjada
    • Request scopes: instagram_basic, pages_show_list
  3. Retrieve the Instagram Business Account ID (IG_USER_ID) via two Graph API calls:
    • First call gets the Page's instagram_business_account object
    • Extract the id field — this is the IG_USER_ID
  4. Exchange for a long-lived token (valid for ~60 days):
    • Uses the short-lived token, APP_ID, and APP_SECRET
    • Returns IG_ACCESS_TOKEN for production use

Lambda Function Changes

The Lambda function at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py already contained the Instagram integration logic but required activation. Key modifications:

  • Environment variable setup: Added IG_USER_ID and IG_ACCESS_TOKEN to the Lambda function configuration via AWS Lambda console
  • No code changes needed: The existing handler checks for these variables and calls Instagram Graph API endpoints when present
  • Async execution pattern: The function queries both DynamoDB (for guest photos) and Instagram Graph API in parallel, merging results by event date

Environment variables are managed via:

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

Token Refresh Strategy

Why long-lived tokens matter: Short-lived tokens expire in hours. Long-lived tokens last ~60 days, suitable for server-to-server integrations. However, they require periodic refresh.

We implemented a monthly refresh approach:

  • Option 1 (Manual): Refresh the token using the same token exchange endpoint, update Lambda configuration
  • Option 2 (Automated): Create an EventBridge scheduled rule that triggers a separate Lambda function monthly to refresh and update the main Lambda's environment variables

The refresh call requires APP_ID, APP_SECRET, and the current IG_ACCESS_TOKEN. No user interaction is needed once configured.

Infrastructure and Deployment

The Lambda function is deployed in us-east-1 and accessed via API Gateway, which routes requests from the CloudFront-distributed HTML frontend. The guest photo page route pattern is /g/{event_id}, where event_id is a date string like 2026-04-29.

Key resources:

  • Lambda function: shipcaptaincrew (runtime: Python 3.x)
  • DynamoDB table: Stores approved guest photos with event_id as partition key
  • API Gateway: Routes HTTPS requests to Lambda
  • CloudFront distribution: Caches static HTML/CSS/JS (index.html at /tools/shipcaptaincrew/index.html)
  • S3 bucket: Origin for CloudFront (exact bucket name redacted for security)

The frontend JavaScript calls the Lambda API endpoint with ?event_id=2026-04-29, receives a JSON response containing both guest photo metadata and Instagram media objects, and renders them in chronological order.

Key Design Decisions

  1. Why not use Basic Display API? Basic Display only returns a limited set of fields (media ID, caption, timestamp, media type, media URL). Graph API returns richer metadata including insights. For a charter photo experience, Graph API is the more extensible choice.
  2. Why long-lived tokens instead of client-side flow? The page doesn't require user login. Server-side token exchange (Lambda) is simpler and more secure than embedding client credentials in frontend code.
  3. Why separate environment variables? Decoupling IG_USER_ID and IG_ACCESS_TOKEN allows independent rotation of credentials and makes it easier to audit which Lambda functions need Instagram access.
  4. Why query both DynamoDB and Instagram? Guest photos are user-uploaded and must be approved (stored in DynamoDB). Instagram posts are