```html

Integrating Instagram Graph API with AWS Lambda: Connecting Guest Photos to Ship Captain Crew Events

What Was Done

We enabled Instagram Graph API integration for the Ship Captain Crew guest photo system to automatically surface @sailjada Instagram posts alongside user-uploaded charter photos. The Lambda function at shipcaptaincrew (us-east-1, account 782785212866) previously had dormant Instagram integration code that returned empty arrays due to missing environment variables. This post walks through the complete setup process: creating the correct Facebook app product configuration, obtaining API credentials, exchanging tokens for long-lived access, and deploying credentials to Lambda.

Technical Details: The Instagram Graph API Setup Flow

Step 1: Add Instagram Graph API Product (Not Messaging)

The critical first mistake to avoid: adding the "Manage messaging" use case grants Direct Message capabilities, not media read permissions. Instead:

  • Navigate to developers.facebook.com/apps
  • Select the sailjada-social application
  • Click Add Product in the left sidebar (bottom section)
  • Search for and select Instagram Graph API (distinct from Basic Display and Messaging)
  • Complete the setup wizard—the product will appear in your left sidebar

Why this matters: The Graph API product is the only one that exposes the instagram_basic scope, required to query media data via the /ig_hashtag_search and /media endpoints. Messaging products restrict you to conversation endpoints.

Step 2: Verify @sailjada Account Type and Link to Facebook Page

Instagram Graph API requires a Business or Creator account linked to a Facebook Page. Verify this in the Instagram app settings, then in your app's Instagram Graph API section:

  • Click API Setup with Instagram Login
  • Click Add Instagram Account
  • Authenticate as @sailjada
  • Authorize the app to access account data

Step 3: Generate Short-Lived Access Token

The Graph API Explorer provides an interactive way to generate initial tokens with the correct scopes:

  • Open developers.facebook.com/tools/explorer
  • Switch the app selector to sailjada-social
  • Click Generate Access Token
  • Select the Facebook Page linked to @sailjada
  • In the permissions prompt, grant: instagram_basic and pages_show_list
  • Copy the resulting token (valid for ~2 hours)

Why short-lived first: Short-lived tokens are safer for initial testing and don't require storing credentials in your Lambda before you verify the workflow works end-to-end.

Step 4: Retrieve IG_USER_ID

The Lambda function needs the numeric Instagram Business Account ID (distinct from the username). Retrieve it via two API calls:


# First, get your Facebook Page ID and its linked Instagram account
curl -s "https://graph.instagram.com/me/accounts?access_token=SHORT_LIVED_TOKEN" \
  | jq '.data[0].id'

# Then fetch the Instagram Business Account ID from that page
curl -s "https://graph.instagram.com/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN" \
  | jq '.instagram_business_account.id'

Store this numeric ID as IG_USER_ID.

Step 5: Exchange for Long-Lived Token (60-Day Validity)

Short-lived tokens expire quickly. Exchange it for a long-lived token using your app credentials:


curl -s "https://graph.instagram.com/access_token" \
  -d "grant_type=fb_exchange_token" \
  -d "client_id=CLIENT_ID" \
  -d "client_secret=CLIENT_SECRET" \
  -d "access_token=SHORT_LIVED_TOKEN"

The response includes a new access_token field—this is your IG_ACCESS_TOKEN, valid for ~60 days. Store this securely in AWS Systems Manager Parameter Store or Secrets Manager (though our current setup uses Lambda environment variables).

Infrastructure: Lambda Configuration

The shipcaptaincrew Lambda function (runtime: Python 3.x, handler: lambda_function.handler in the deployment package) expects two environment variables:

  • IG_USER_ID: The numeric Instagram Business Account ID
  • IG_ACCESS_TOKEN: The 60-day long-lived access token

Update the function configuration using AWS CLI:


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

The Lambda code (located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py) contains the handler function which:

  • Parses incoming requests to extract event_id from paths like /g/2026-04-29
  • Queries approved guest photos from the DynamoDB table
  • Calls Instagram Graph API (if credentials are present) to fetch @sailjada posts from the same date window
  • Merges and returns both datasets as JSON

Architecture Pattern: Dormant Feature Gate

The Instagram integration uses a defensive pattern: if environment variables are missing or invalid, the function returns an empty array for Instagram posts rather than failing the entire request. This allows the guest photo system to function independently while Instagram integration is optional. Once credentials are set, the API automatically enables the feature without code changes.

Key Decisions

  • Long-lived tokens over manual refresh: Rather than implementing EventBridge-triggered token refresh logic immediately, we're using the 60-day window. Production can add automatic monthly refresh later.
  • Environment variables over Secrets Manager: For simplicity in this phase, credentials are stored as Lambda environment variables. For higher security, migrate to AWS Secrets Manager and update the handler to retrieve values at runtime.
  • Business Account linking: @sailjada must use a Business or Creator account (not a personal account) to appear in Graph API queries. Personal accounts cannot be accessed via this endpoint.

Verification

After updating Lambda configuration, test the integration by visiting the guest photo page:


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

Check CloudWatch Logs for the Lambda