```html

Integrating Instagram Graph API with Lambda: Connecting Guest Charter Photos to Social Media

What Was Done

We integrated Instagram Graph API into the ShipCaptainCrew guest photo gallery system to automatically surface @sailjada Instagram posts alongside user-uploaded charter photos. The guest page at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29) now has the infrastructure to pull Instagram media from a configurable time window, displaying both sources in a unified gallery.

The work involved three key areas: registering the correct Instagram Graph API product in the Facebook Developer App, establishing an OAuth token exchange flow, and deploying environment variables to the Lambda function that renders the guest page.

Technical Details

Application Structure

The guest photo system is deployed across two components:

  • Lambda Function: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py — handles API requests, database queries for approved guest photos, and conditional Instagram media fetching
  • Frontend: /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/index.html — static HTML/JavaScript that queries the Lambda endpoint and renders the combined gallery

The Lambda function exposes a route at /g/{event_id} that returns JSON with two arrays: approved guest photos from DynamoDB and Instagram posts from the Graph API (when credentials are present).

Instagram Graph API Product Registration

The initial setup required adding the correct product type to the Facebook Developer app (ID: sailjada-social). This was critical because the app had a "Messaging" product configured, which grants Direct Message scopes but not media-read permissions.

The required product: Instagram Graph API (distinct from Basic Display and Messaging).

Steps taken:

  1. Navigate to developers.facebook.com/apps → Select app sailjada-social
  2. Left sidebar → Add Product
  3. Find Instagram in the product catalog → Click Set Up
  4. Select Instagram Graph API as the access type
  5. Once added, the product appears in the sidebar under Instagram Graph API

Business Account Linking

The @sailjada Instagram account must be a Business or Creator account and linked to a Facebook Business Page for Graph API access. Within the Instagram Graph API settings:

  • Navigate to API Setup → Instagram Login
  • Click Add Instagram Account
  • Log in with @sailjada credentials to authorize the app
  • Select the Business Page associated with @sailjada

Token Generation Flow

Instagram Graph API requires two tokens: a short-lived token (valid for 1 hour) generated during development, and a long-lived token (valid for 60 days) for production use.

Short-lived token generation:

  1. Visit developers.facebook.com/tools/explorer
  2. Select app dropdown → choose sailjada-social
  3. Click Generate Access Token
  4. Select the Facebook Business Page linked to @sailjada
  5. Confirm scopes: instagram_basic, pages_show_list
  6. Copy the generated token

Retrieving IG_USER_ID:

With the short-lived token, query the Facebook Graph API to find the Instagram Business Account ID:

curl -s "https://graph.instagram.com/me?fields=instagram_business_account&access_token=TOKEN" | jq

Response structure:

{
  "instagram_business_account": {
    "id": "17841401234567890"
  },
  "id": "123456789"
}

Extract the instagram_business_account.id value as IG_USER_ID.

Long-lived token exchange:

Exchange the short-lived token for a long-lived token valid for 60 days:

curl -s "https://graph.instagram.com/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&access_token=SHORT_LIVED_TOKEN" | jq

Response contains a new access_token with extended validity. Store this as IG_ACCESS_TOKEN.

Infrastructure & Deployment

Lambda Function Configuration

The Lambda function shipcaptaincrew in region us-east-1 (account 782785212866) was updated with environment variables:

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

Before this change, the Lambda had stubbed Instagram support: if either environment variable was missing, the function returned an empty Instagram array.

The relevant Lambda code path (in lambda_function.py) checks for these variables at request time and makes a conditional call to the Instagram Graph API endpoint:

GET https://graph.instagram.com/{IG_USER_ID}/media?access_token={IG_ACCESS_TOKEN}

Logging & Verification

After deployment, logs were tailed to verify successful API calls:

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

The guest page was tested by making a direct request to verify both guest photos and Instagram posts appear:

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

Key Decisions

  • 60-day token strategy: Rather than implementing a full OAuth refresh flow, we opted for long-lived tokens with a monthly manual refresh. This reduces Lambda cold-start complexity and avoids storing refresh tokens in environment variables.
  • Conditional Instagram fetching: The Lambda only calls Instagram Graph API if both credentials are present. This allows the guest page to degrade gracefully (showing only guest photos) if credentials are missing or expired.
  • Time-window filtering: Instagram posts are filtered client-side in index.html to match the event date. This avoids querying Instagram's full media history repeatedly.
  • Environment variables over secrets: While AWS Secrets Manager would be more secure, environment variables on the Lambda are sufficient for read-only API credentials with time-limited validity.

What's Next