```html

Integrating Instagram Graph API with AWS Lambda: Authenticating @sailjada's Business Account for Guest Photo Feeds

What Was Done

We implemented Instagram Graph API authentication in the shipcaptaincrew Lambda function to surface @sailjada's Instagram posts alongside guest-uploaded charter photos on the event guest page at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The integration was previously dormant—returning empty arrays when environment variables were missing. This walkthrough documents the exact steps to activate it using a long-lived access token and establish a sustainable refresh strategy.

Technical Details: The Authentication Flow

Step 1: Add the Correct Product to Your Facebook App

The initial blocker was product selection. The app sailjada-social (ID: 1688884572116630) had the "Messaging" use case added, which grants the wrong OAuth scopes. Messaging is for DMs; we need media reads.

  • Navigate to developers.facebook.com/apps
  • Select sailjada-social
  • Click Add Product in the left sidebar
  • Search for Instagram and select Instagram Graph API (not Basic Display)

Step 2: Connect the Instagram Business Account

@sailjada must be a Business or Creator account linked to a Facebook Page. Once the product is added:

  • In Instagram Graph API → API setup
  • Click Add Instagram account
  • Authenticate as @sailjada (the account owner)

Step 3: Generate a Short-Lived Token via Graph API Explorer

The short-lived token is a stepping stone to the long-lived token. It's good for ~1 hour and grants only the scopes you explicitly request.

  • Go to developers.facebook.com/tools/explorer
  • Select app dropdown → choose sailjada-social
  • Click Generate Access Token
  • In the permissions modal, select:
    • instagram_basic (read media, user profile)
    • pages_show_list (enumerate linked pages)
  • Confirm and copy the token

Step 4: Retrieve IG_USER_ID

With the short-lived token, query the Facebook Graph API to find the Instagram Business Account ID tied to your Facebook Page.

# Get the Facebook Page ID (the one connected to @sailjada)
curl -s "https://graph.facebook.com/v18.0/me/accounts?access_token=SHORT_LIVED_TOKEN" | jq '.data[] | {id, name}'

# Note the page_id from the response, then retrieve the Instagram account ID:
curl -s "https://graph.facebook.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN" | jq '.instagram_business_account.id'

The returned id is your IG_USER_ID. Example: 17841402023456789

Step 5: Exchange Short-Lived Token for Long-Lived Token

Long-lived tokens are valid for ~60 days and are suitable for backend services. This is what we store in Lambda environment variables.

curl -s "https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id=1688884572116630&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN" | jq '.access_token'

The returned access_token is your IG_ACCESS_TOKEN. Store this securely.

Infrastructure: Lambda Environment Configuration

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

The handler checks for two environment variables at runtime. If both are set, it makes authenticated requests to the Instagram Graph API. If missing, it gracefully returns an empty array (the previous dormant state).

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

Verify the update:

aws lambda get-function-configuration \
  --function-name shipcaptaincrew \
  --region us-east-1 | jq '.Environment.Variables'

Code Integration in Lambda

Within lambda_function.py, the guest page handler (around line 1060+) calls an internal function to fetch Instagram posts when IG_USER_ID and IG_ACCESS_TOKEN are present:

def fetch_instagram_posts(user_id, access_token, event_date):
    """Fetch Instagram media for the given date."""
    if not user_id or not access_token:
        return []
    
    url = f"https://graph.instagram.com/v18.0/{user_id}/media"
    params = {
        'fields': 'id,caption,media_type,media_url,timestamp,permalink',
        'access_token': access_token
    }
    response = requests.get(url, params=params)
    if response.status_code != 200:
        # Log the error, return empty
        return []
    
    # Filter posts by event_date window
    posts = [...]
    return posts

The guest page template (index.html) renders Instagram posts and guest photos side-by-side, fetched via API response.

Key Decisions

  • Long-lived tokens over short-lived: Backend services need tokens that persist across multiple invocations. The 60-day window is appropriate for a scheduled monthly refresh strategy.
  • Environment variables for secrets: Storing IG_USER_ID and IG_ACCESS_TOKEN as Lambda env vars allows us to rotate tokens without redeploying code. AWS Secrets Manager would be the next step for production hardening.
  • Graceful degradation: If env vars are missing, the page still renders guest photos. This keeps the feature non-blocking for deployments.
  • Graph API v18.0: Pinning the API version ensures predictable field names and behavior across token refreshes.

What's Next: Token Refresh Strategy

Long-lived tokens expire after 60 days. Implement a monthly refresh:

  • Option 1 (Manual):