```html

Integrating Instagram Graph API with AWS Lambda: Connecting Guest Photo Galleries to Social Media

Overview

The Ship Captain Crew guest photo gallery system needed to display Instagram posts from @sailjada alongside user-uploaded charter photos. While the Lambda function had dormant Instagram integration code, the Facebook App lacked proper Graph API configuration and credential management. This post walks through the exact steps taken to activate Instagram media retrieval, including product setup, token generation, and Lambda environment variable configuration.

What Was Done

Three core changes were made to enable Instagram integration:

  • Added Instagram Graph API product to the Facebook app sailjada-social (previously only Messaging was configured)
  • Generated and exchanged access tokens using the Graph API Explorer with proper scopes
  • Updated the Lambda function environment variables IG_USER_ID and IG_ACCESS_TOKEN to activate dormant code in lambda_function.py

Technical Details: Instagram Graph API Setup

Step 1: Adding the Correct Product to the Facebook App

The initial blocker: the sailjada-social app had "Instagram Messaging" added, which grants DM-related scopes but NOT instagram_basic (required to read media). The solution required adding a separate product:

  • Navigate to developers.facebook.com/apps → select sailjada-social
  • Left sidebar → Add Product → search for "Instagram"
  • Select Instagram Graph API (distinct from Basic Display and Messaging)
  • Complete setup wizard, which adds the product to your app's available APIs

Why this matters: Each Facebook product grants different OAuth scopes. Instagram Graph API specifically provides instagram_basic, pages_show_list, and media-reading capabilities. Messaging is for conversation threads, not image galleries.

Step 2: Account Connection and Token Generation

Once the product was added, the next step connected the actual Instagram business account:

  • Inside the Instagram Graph API product settings → API setup with Instagram login
  • Click Add Instagram account → authenticate as @sailjada
  • Confirm the account is linked to a Facebook Business Page (required for media APIs)

After account connection, a short-lived access token was generated via the Graph API Explorer:

https://developers.facebook.com/tools/explorer
- Select app: sailjada-social
- Select access token type: User
- Scopes: instagram_basic, pages_show_list
- Generate Token

Step 3: Retrieving IG_USER_ID

The Instagram Graph API requires both a user ID (IGID format, ~17 digits) and an access token. The user ID was retrieved via two chained API calls:

# First, get the Facebook Page ID linked to @sailjada
curl -s "https://graph.instagram.com/me?fields=id,username&access_token=YOUR_TOKEN" \
  | jq '.id'

# Then, fetch the Page's linked Instagram business account
curl -s "https://graph.instagram.com/PAGE_ID?fields=instagram_business_account&access_token=YOUR_TOKEN" \
  | jq '.instagram_business_account.id'

The returned ID becomes IG_USER_ID (stored as Lambda environment variable).

Step 4: Exchanging for Long-Lived Token

The Graph API Explorer generates short-lived tokens (1 hour). For production use, a 60-day long-lived token was generated:

curl -s "https://graph.instagram.com/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=SHORT_LIVED_TOKEN" \
  | jq '.access_token'

This returned token is valid for 60 days and is used as IG_ACCESS_TOKEN in Lambda.

Infrastructure and Lambda Configuration

Lambda Function Details

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

The Lambda function (runtime: Python 3.11+, region: us-east-1, account: 782785212866) contains dormant Instagram code that checks for these environment variables:

  • IG_USER_ID: Instagram business account ID (required to query media)
  • IG_ACCESS_TOKEN: Long-lived Graph API token (permissions: instagram_basic, media access)

When both variables are set, the Lambda function queries Instagram's /ig_hashtag_search endpoint filtered by event date/time window, enriching the guest photo gallery with official charter Instagram posts.

Environment Variable Update

Configuration was applied via AWS CLI:

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

Verification

The integration was tested by accessing the guest photo page at:

https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29

AWS CloudWatch logs for the Lambda function verified successful API calls:

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

Key Architectural Decisions

Why Long-Lived Tokens Instead of App-to-App Flows

Instagram Graph API supports two token types: user tokens (60-day refresh required) and app tokens (non-expiring but read-only). User tokens were chosen because they grant full instagram_basic scope, necessary for querying the @sailjada business account's media. This requires monthly token refresh but enables future features like insights and video metadata.

Token Refresh Strategy

The 60-day window requires a refresh mechanism. Two approaches were considered:

  • Manual refresh (chosen initially): Operator runs the exchange call monthly, updates Lambda env vars. Simple, reduces complexity.
  • EventBridge automation (future): CloudWatch Events trigger a Lambda that auto-refreshes tokens. Would require secure storage (Secrets Manager) and adds operational overhead.

For now, manual refresh via Lambda update command is sufficient given the low-frequency nature of charter events.

Scope Minimization

Only two OAuth scopes were requested: instagram_basic (read media metadata) and pages_show_list (enumerate linked Pages). No access to DMs, insights, or shop features was granted, following the principle of least privilege