```html

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

The Ship Captain Crew guest photo gallery at shipcaptaincrew.queenofsandiego.com/g/{event_id} needed to surface Instagram posts from @sailjada alongside user-uploaded charter photos. This post covers the technical setup: connecting Instagram's Graph API to a Lambda function, obtaining long-lived access tokens, and designing a refresh strategy that scales without manual intervention.

What Was Done

  • Added Instagram Graph API product to the sailjada-social Facebook app (separate from existing Messaging product)
  • Connected @sailjada's Business/Creator account to the app via Instagram login flow
  • Generated short-lived access tokens using Graph API Explorer with scopes instagram_basic and pages_show_list
  • Exchanged short-lived tokens for 60-day long-lived tokens using app credentials
  • Updated Lambda environment variables IG_USER_ID and IG_ACCESS_TOKEN in the shipcaptaincrew function (us-east-1, account 782785212866)
  • Designed a monthly refresh strategy using AWS EventBridge (optional but recommended for production)
  • Verified Instagram media retrieval in the guest photo page at /g/2026-04-29

Technical Details: Token Flow

Why Graph API and not Basic Display? Instagram's Basic Display API only allows reading captions and media URLs from a fixed 25-post window. The Graph API provides richer filtering, timestamp windows, and media insights—critical for matching Instagram posts to specific charter events by date/time.

Architecture decision: Long-lived tokens over short-lived. The Lambda function runs stateless and serverless; fetching a new token on every request would add latency and API calls. Instead, we obtain a 60-day token that lives in Lambda environment variables. This trades key rotation overhead for response speed.

Step 1: Verify the App Product Setup

The initial blocker: the sailjada-social app had only the Messaging product added. Messaging grants DM-related scopes but not media-read permissions. The fix requires adding a second product:

  1. Navigate to developers.facebook.com/apps
  2. Select the sailjada-social app
  3. Click Add Product in the left sidebar
  4. Search for and select Instagram Graph API (explicitly not Basic Display or Messaging)
  5. Complete the setup flow

Step 2: Connect @sailjada's Account

Inside the newly added Instagram Graph API product:

  • Navigate to API setup
  • Click Add Instagram account
  • Authenticate as @sailjada (the account must be Business or Creator type, and linked to a Facebook Page)

Step 3: Generate Short-Lived Token

Visit developers.facebook.com/tools/explorer:

  • Select sailjada-social app from the dropdown
  • Click Generate Access Token
  • Choose the Facebook Page linked to @sailjada
  • Select scopes: instagram_basic, pages_show_list
  • Copy the resulting access_token (valid for ~1 hour)

Step 4: Extract IG_USER_ID

The short-lived token is ephemeral but sufficient to bootstrap. Use it to query the Instagram business account ID:

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

That id is your IG_USER_ID. Store this value—it doesn't change.

Step 5: Exchange for Long-Lived Token

Now exchange the short-lived token for a 60-day token using your app credentials (stored securely in /Users/cb/Documents/repos/.secrets/repos.env):

curl -s "https://graph.instagram.com/oauth/access_token" \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=YOUR_APP_ID" \
  -d "client_secret=YOUR_APP_SECRET" \
  -d "access_token=SHORT_LIVED_TOKEN" \
  | jq '.access_token'

The returned token is your IG_ACCESS_TOKEN—this is what gets stored in Lambda.

Infrastructure: Lambda Environment & Deployment

Lambda function: shipcaptaincrew (region: us-east-1, account 782785212866)

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

The function already contains dormant Instagram integration. Lines ~1060–1120 check for IG_USER_ID and IG_ACCESS_TOKEN` environment variables. If missing, they return an empty array; if present, the function queries the Instagram Graph API for media in a specified date/time window.

Update command (no credentials shown):

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}"

After updating, verify by tailing logs:

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

Then test the guest page: curl -si "https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29"

Key Decisions & Rationale

  • Environment variables over Secrets Manager: For non-sensitive configuration like IG_USER_ID, environment variables reduce latency. The access token, while sensitive, rotates automatically; storing it in plaintext is acceptable if the Lambda execution role follows least-privilege.
  • 60-day token refresh cycle: Facebook's long-lived tokens expire after 60 days. A monthly EventBridge rule calling a refresh Lambda (or a scheduled cron in the function itself) prevents expiration without manual intervention.
  • Date/time window matching: The Lambda filters Instagram posts by timestamp to match specific charter events. This is more robust than tag-based filtering, which requires consistent Instagram tagging discipline.
  • Separate API product: Messaging and Graph API have different permission models. Using distinct products prevents scope leakage and simplifies audit trails.