```html

Integrating Instagram Graph API with AWS Lambda: Building a Photo Gallery Bridge for Charter Events

What Was Done

We implemented Instagram Graph API integration into an existing AWS Lambda function that serves guest-uploaded charter photos for the Ship Captain Crew event platform (shipcaptaincrew.queenofsandiego.com). The Lambda function, deployed in us-east-1 (account 782785212866), now has the infrastructure to fetch and display official @sailjada Instagram posts alongside user-submitted photos from matching event dates and time windows.

The integration was previously dormant—the Lambda contained conditional logic that returned an empty array when environment variables for Instagram credentials were missing. This session focused on the foundational steps: correctly configuring the Instagram Graph API product in the Facebook App Console, obtaining proper authentication credentials, and documenting the token refresh lifecycle.

Technical Details: Why the Wrong Product Setup Was a Blocker

The initial configuration added the Instagram Messaging API product to the sailjada-social app. While this product grants access to Instagram Direct Messages, it does not include the instagram_basic scope required to read media metadata (captions, timestamps, media URLs). The Messaging use case is designed for bot automation and customer support workflows, not media library queries.

The correct product is Instagram Graph API, which provides access to:

  • instagram_basic — read user profile data and media metadata
  • pages_show_list — list Facebook Pages linked to the app user
  • Media endpoint: GET /{ig-user-id}/media to retrieve posts and their details

This distinction is critical because the token exchange flow validates requested scopes against the product's grant set. A token generated under Messaging will be rejected when the Lambda attempts to query media endpoints.

Authentication Flow and Token Lifecycle

Instagram Graph API uses a two-tier token system:

  • Short-lived tokens (1 hour) — generated interactively via Graph API Explorer or OAuth flows
  • Long-lived tokens (60 days) — obtained by exchanging a short-lived token with app credentials

The process:

  1. Log in to developers.facebook.com/tools/explorer as a developer on the app
  2. Select the sailjada-social app and the Facebook Page linked to @sailjada
  3. Generate an access token with scopes: instagram_basic,pages_show_list
  4. Use this short-lived token to call the Accounts endpoint:
    GET /me/accounts?fields=instagram_business_account
    This returns the Instagram Business Account ID (stored as IG_USER_ID)
  5. Exchange the short-lived token for a long-lived token:
    GET /oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&fb_exchange_token={SHORT_LIVED_TOKEN}

The long-lived token is then stored in the Lambda environment as IG_ACCESS_TOKEN and used in the handler to fetch media. Since Instagram long-lived tokens expire after 60 days of non-use, the deployment includes a note to refresh via the same exchange call on a monthly cadence (optional automation via EventBridge).

Infrastructure and Configuration

Lambda Function:

  • Name: shipcaptaincrew
  • Region: us-east-1
  • Runtime: Python 3.x
  • File: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py

Environment Variables (to be added):

  • IG_USER_ID — Instagram Business Account ID (17-digit numeric string)
  • IG_ACCESS_TOKEN — Long-lived Graph API token

Update command (template):

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

Frontend:

  • HTML: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html
  • Route: /g/{event_id} (e.g., /g/2026-04-29)
  • API calls: Lambda handler routes requests and merges approved guest photos with Instagram media

Verification:

CloudWatch Logs can be monitored to confirm successful API calls:

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

Key Architectural Decisions

Why Lambda environment variables instead of Secrets Manager? For this use case, the tokens are non-critical metadata (media URLs, captions) and the 60-day refresh window is manageable. Secrets Manager adds operational overhead for a lower-risk credential type. If we scale to handle DMs or account modifications, migration to Secrets Manager would be warranted.

Why long-lived tokens at all? The handler must refresh media on each request without user interaction. Short-lived tokens expire in 1 hour, requiring a backend token-refresh flow. Long-lived tokens (60 days) simplify the logic and reduce API calls. The trade-off is a monthly refresh task (can be automated via EventBridge scheduler + a separate Lambda).

Why check for missing credentials gracefully? The existing Lambda code contains:

if not IG_USER_ID or not IG_ACCESS_TOKEN:
    instagram_posts = []

This allows safe deployment before credentials are available, preventing runtime errors during dev iterations.

What's Next

  • Implement token refresh automation (EventBridge rule + scheduled Lambda invocation)
  • Add error handling for expired tokens (catch 400/401 responses, log to CloudWatch)
  • Test the guest page at /g/2026-04-29 to verify photo merging and timestamp filtering
  • Monitor Lambda logs for API quota consumption (Instagram rate limits: 200 calls/hour per token)
  • Document the 60-day refresh cadence in the deployment wiki

Summary: This session unblocked Instagram Graph API integration by correcting the app product configuration, defining the credential exchange flow, and establishing the environment variables needed for the Lambda handler. The foundation is now in place for official @sailjada photos to appear alongside guest uploads on charter event pages.

```