```html

Integrating Instagram Graph API with AWS Lambda: Building a Charter Photo Gallery with Social Media Feeds

What Was Done

We implemented Instagram Graph API integration into the ShipCaptainCrew guest photo gallery system, enabling the `/g/{event_id}` route (e.g., `/g/2026-04-29`) to display approved guest-uploaded charter photos alongside @sailjada Instagram posts from matching date/time windows. The integration connects a Facebook app (`sailjada-social`) to a Business Instagram account, retrieves media metadata via the Graph API, and renders results client-side in the existing HTML interface.

Technical Architecture

Lambda Function Structure

The core application lives in /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py, deployed to AWS Lambda function shipcaptaincrew in us-east-1 (account 782785212866).

The Lambda handler routes HTTP requests to three primary endpoints:

  • / — serves index.html (static guest photo upload interface)
  • /guest/{event_id} — retrieves approved photos for a specific charter event
  • /g/{event_id} — the public-facing gallery view that combines guest photos + Instagram media

The Instagram integration logic is dormant by default. When environment variables IG_USER_ID and IG_ACCESS_TOKEN are absent, the Lambda returns an empty array for Instagram media, allowing the service to function without social credentials. This graceful degradation ensures the photo gallery remains operational during credential rotation or API outages.

Instagram Graph API Integration Points

The Lambda makes HTTP requests to the Instagram Graph API endpoint:

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

This retrieves media metadata for the authenticated Business account. The timestamp is parsed and compared against the event date to filter posts within the same day or configurable time window. The architecture follows a pull model: the Lambda queries Instagram on-demand when a guest accesses `/g/{event_id}`, rather than pushing data or maintaining a cache.

Infrastructure & Deployment

AWS Resources

  • Lambda Function: shipcaptaincrew (runtime: Python 3.x, region: us-east-1)
  • CloudWatch Logs: /aws/lambda/shipcaptaincrew for debugging API calls and token errors
  • Environment Variables: Managed via Lambda function configuration
    • IG_USER_ID — numeric ID of the Business Instagram account (obtained via Graph API explorer)
    • IG_ACCESS_TOKEN — long-lived token (60-day validity) from Facebook app

To update the Lambda configuration with credentials:

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

Logs are monitored via:

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

Frontend: Static HTML & Client-Side Integration

The file index.html in the same directory serves as the guest upload form. A separate public gallery view (loaded at `/g/{event_id}`) is rendered server-side or client-side depending on the Lambda's response structure. The current implementation returns JSON containing:

{
  "guest_photos": [ { "url": "...", "uploader": "...", "timestamp": "..." } ],
  "instagram_posts": [ { "id": "...", "caption": "...", "media_url": "...", "timestamp": "..." } ]
}

JavaScript in the gallery page merges these arrays, sorts by timestamp, and renders a unified feed.

Facebook App Configuration

The integration requires a Facebook App (sailjada-social) with the Instagram Graph API product added, not the Messaging or Basic Display products. This is a critical distinction: the Messaging product grants webhook and messaging scopes, not media read access.

Setup Steps

  1. Navigate to developers.facebook.com/apps and select sailjada-social
  2. Click Add Product in the left sidebar
  3. Select Instagram Graph API and complete setup
  4. Under Instagram Graph API → API Setup, add the @sailjada Instagram account (must be a Business or Creator account linked to a Facebook Page)

Token Generation & Exchange

Instagram tokens follow a three-step flow:

  1. Generate short-lived token: Use the Graph API Explorer at developers.facebook.com/tools/explorer, select the app and Facebook Page, and request scopes instagram_basic and pages_show_list
  2. Retrieve IG_USER_ID: Query the Page's Instagram business account using the short-lived token:
    curl "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"
    The response contains instagram_business_account.id, which is IG_USER_ID
  3. Exchange for long-lived token: POST to the token endpoint with app credentials to convert the short-lived token to a 60-day token:
    curl -X POST "https://graph.instagram.com/oauth/access_token?grant_type=fb_exchange_token&client_id={APP_ID}&client_secret={APP_SECRET}&access_token={SHORT_LIVED_TOKEN}"
    The returned access_token is IG_ACCESS_TOKEN

Key Design Decisions

  • On-demand vs. scheduled refresh: The Lambda queries Instagram synchronously when a guest loads `/g/{event_id}`, avoiding the complexity of maintaining a separate cache. For high-traffic galleries, a CloudFront cache header (e.g., 300 seconds) can reduce API calls.
  • Graceful degradation: Missing credentials don't break the service; they simply return an empty Instagram array, allowing the guest photo gallery to function independently.
  • Token expiration strategy: The 60-day long-lived token requires monthly refresh. A scheduled EventBridge rule (optional) can trigger a Lambda to exchange the token without user interaction, or manual renewal can occur via the same `curl` command above.
  • Data filtering: Timestamps are compared in UTC to ensure cross-timezone consistency between guest uploads and Instagram posts.

What's Next

  • Cache invalidation: Implement CloudFront cache