Integrating Instagram Graph API with Lambda: Connecting @sailjada Media to Guest Photo Pages

What Was Done

We enabled the dormant Instagram integration in the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to surface @sailjada Instagram posts alongside guest-uploaded charter photos on event-specific pages at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration was architecturally present but non-functional due to missing environment variables (IG_USER_ID and IG_ACCESS_TOKEN). This walkthrough covers credential acquisition, token lifecycle management, and Lambda configuration.

Technical Details: The Instagram Graph API Approach

Why Graph API, not Basic Display? The Instagram Basic Display API only returns media URLs and captions—no metadata like creation timestamps. Since the guest photo page filters posts by event date/time windows, we need the full media object including timestamp fields. Instagram Graph API (available to Business/Creator accounts) provides this metadata natively.

Product Setup in Facebook Developer Console

The initial blocker was an incorrect product configuration. The app had "Messaging" added (for DM functionality), which doesn't grant the instagram_basic scope required to read media. The solution required adding Instagram Graph API as a separate product:

  • Navigate to developers.facebook.com/apps
  • Select the sailjada-social app
  • Click Add Product in the left sidebar
  • Search for and select Instagram Graph API (distinct from Basic Display or Messaging)
  • The product appears in the sidebar under Instagram Graph API

Account Linking and Token Generation

Instagram Graph API requires connecting a Business or Creator account to a Facebook Page. The @sailjada account is a Creator account linked to a corresponding Facebook Page. Token generation follows this flow:

  1. Short-lived token (2 hours): Generated via developers.facebook.com/tools/explorer with scopes instagram_basic and pages_show_list. This token is ephemeral and used only for the next step.
  2. Extract IG_USER_ID: Two API calls retrieve the business account ID:
    # First call: Get Facebook Page ID from the token
    curl -X GET "https://graph.instagram.com/v18.0/me/accounts?access_token={SHORT_LIVED_TOKEN}"
    
    # Extract the page_id from the response, then:
    # Second call: Get Instagram business account ID
    curl -X GET "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"
    
    # The "id" field inside instagram_business_account is IG_USER_ID
  3. Long-lived token (60 days): Exchanged using app credentials:
    curl -X GET "https://graph.instagram.com/v18.0/oauth/access_token" \
      -d "grant_type=ig_refresh_token" \
      -d "access_token={SHORT_LIVED_TOKEN}"
    
    # Returns long-lived access_token valid for 60 days

Infrastructure & Lambda Configuration

Lambda Function Details

Function name: shipcaptaincrew
Region: us-east-1
AWS Account: 782785212866

The function's handler reads two environment variables that were previously unset:

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

Configuration update uses the AWS CLI:

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

The handler logic (in the Lambda runtime) checks for these variables on invocation. If present, it makes paginated requests to the Instagram Graph API endpoint:

https://graph.instagram.com/v18.0/{IG_USER_ID}/media?fields=id,caption,media_type,timestamp,media_url,permalink&access_token={IG_ACCESS_TOKEN}

Results are filtered by comparing the timestamp field against the event date parameter extracted from the URL path (e.g., /g/2026-04-29).

Key Architecture Decisions

Why 60-day tokens instead of short-lived? Long-lived tokens eliminate daily credential rotation logic and reduce call overhead. The tradeoff is monthly refresh burden (detailed below).

Why Graph API pagination? The endpoint returns a maximum of 50 media objects per request. Pagination via the after cursor parameter ensures we capture all posts within a rolling time window (typically 30 days around the event date), even if @sailjada is prolific.

Why filter by timestamp in Lambda, not Graph API? Graph API doesn't support direct timestamp range filtering. Client-side filtering in the Lambda handler is simpler than managing temporary storage for token refresh state.

Token Lifecycle & Refresh Strategy

Long-lived tokens expire after 60 days. Refresh requires re-executing the exchange call using the APP_ID and APP_SECRET (stored securely in AWS Secrets Manager or Parameter Store, not in Lambda environment):

  • Manual monthly refresh: Set a calendar reminder to re-run the exchange call and update Lambda environment variables.
  • Automated refresh (optional): EventBridge scheduled rule (e.g., cron: 0 0 15 * ? *, 15th of each month) triggers a helper Lambda that calls the exchange endpoint and updates the shipcaptaincrew function's environment.

For now, manual refresh is acceptable given the low operational overhead of a single social account.

What's Next

After updating the Lambda environment variables, verify the integration by navigating to shipcaptaincrew.queenofsandiego.com/g/2026-04-29 (or any recent charter date). Guest-uploaded photos should render alongside @sailjada posts from that day. CloudWatch Logs for the function (log group: /aws/lambda/shipcaptaincrew) will show any API errors if token refresh is needed or if pagination logic encounters rate limits.

Monitor for Instagram API deprecations; Meta typically provides 6-month notice before sunsetting older API versions. The current endpoint version (v18.0) is stable, but subscription to Meta's developer changelog is recommended.