```html

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

This post documents the integration of Instagram's Graph API into a guest photo management system running on AWS Lambda. The goal was to surface @sailjada Instagram posts alongside user-uploaded charter photos on event-specific pages, all served through a serverless architecture.

What Was Done

We enabled Instagram media fetching in an existing Lambda function that powers the guest photo gallery at shipcaptaincrew.queenofsandiego.com/g/{event_id}. Previously, the Lambda had placeholder code that returned an empty array when Instagram credentials were missing. Now it fetches real Instagram posts from a specific business account and merges them with approved guest uploads in a unified timeline.

  • Modified /tools/shipcaptaincrew/lambda_function.py to activate Instagram Graph API integration
  • Updated /tools/shipcaptaincrew/index.html to render Instagram media alongside guest photos
  • Configured Lambda environment variables: IG_USER_ID and IG_ACCESS_TOKEN
  • Established token refresh strategy for 60-day long-lived access tokens

Technical Details: Instagram Graph API Integration

The Setup Flow

Instagram Graph API requires three prerequisites before your Lambda can fetch media:

  1. Add the correct product to your app: Navigate to developers.facebook.com/apps → sailjada-social → Add Product. Select Instagram Graph API (not Basic Display or Messaging). The Messaging product only grants DM permissions, not media read access.
  2. Connect the business account: In Instagram Graph API → API setup with Instagram login, click Add Instagram account and log in as @sailjada. This account must be a Business or Creator account linked to a Facebook Page.
  3. Generate tokens: Use the Facebook Graph API Explorer to generate an initial short-lived access token with scopes instagram_basic and pages_show_list.

Token Generation Commands

Once you have a short-lived token from the Explorer, retrieve the Instagram User ID (the account's internal identifier):

curl -s "https://graph.instagram.com/v18.0/me/accounts?access_token=YOUR_SHORT_LIVED_TOKEN" \
  | jq '.data[0].id'

Then extract the actual Instagram User ID from the business account object:

curl -s "https://graph.instagram.com/v18.0/PAGE_ID?fields=instagram_business_account&access_token=YOUR_SHORT_LIVED_TOKEN" \
  | jq '.instagram_business_account.id'

Exchange the short-lived token for a long-lived one (valid for 60 days):

curl -s "https://graph.instagram.com/v18.0/oauth/access_token" \
  -X POST \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=YOUR_APP_ID" \
  -d "client_secret=YOUR_APP_SECRET" \
  -d "access_token=YOUR_SHORT_LIVED_TOKEN"

Infrastructure & Lambda Configuration

Environment Variables

Update the Lambda function configuration using the AWS CLI (or Console):

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

The Lambda (account 782785212866, region us-east-1) reads these environment variables on each invocation.

Lambda Handler Flow

In lambda_function.py, the handler processes requests to paths like /g/2026-04-29:

  • Extracts the event date from the URL path
  • Queries the approved guest photo database (DynamoDB or S3) for uploads within a time window
  • If IG_USER_ID and IG_ACCESS_TOKEN are set, calls Instagram Graph API to fetch media from that date
  • Merges both datasets, sorts by timestamp, and returns JSON to the frontend
  • Frontend renders a unified photo grid in index.html

S3 & CloudFront Delivery

The index.html is served from an S3 bucket behind CloudFront. When you modify the HTML to handle new Instagram data fields, follow this deployment pattern:

# After local edits to index.html:
aws s3 cp /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html \
  s3://queenofsandiego-shipcaptaincrew/ \
  --cache-control "max-age=300"

# Invalidate CloudFront cache (replace with actual dist ID):
aws cloudfront create-invalidation \
  --distribution-id E1ABC2DEF3GHI \
  --paths "/g/*" "/"

Key Architectural Decisions

Why 60-Day Tokens, Not Infinite?

Instagram Graph API only offers two token lifespans:

  • Short-lived (1 hour): For immediate use in exploratory calls
  • Long-lived (60 days): For production systems

We chose long-lived tokens because they eliminate constant re-authentication while being short enough to force a security refresh monthly. This balance reduces operational overhead (no daily Lambda calls to refresh) while maintaining reasonable security posture.

Why Merge in the Lambda, Not on the Frontend?

The Lambda merges Instagram posts with guest photos server-side because:

  • Credentials stay server-only; frontend never handles API keys
  • Single JSON response simplifies frontend logic (just iterate and render)
  • Easier to add filtering, deduplication, or timestamp-based sorting once on the backend

Why Separate Instagram Graph API Product?

The Messaging product initially added to your app only grants instagram_messaging scope—suitable for chatbots, not media access. Instagram Graph API is a distinct product that grants instagram_basic (read media, user info) and other media-related scopes. This separation exists in Meta's platform design to enforce principle of least privilege.

Verification & Testing

After deploying environment variables, test the endpoint directly:

curl -s "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29" | jq '.instagram_posts'

If Instagram posts appear in the response, the integration is live. Check Lambda logs for any API call failures:

aws logs tail /aws/lambda/shipcaptaincrew --region us-east-