```html

Integrating Instagram Graph API with AWS Lambda: Guest Photo Gallery Architecture

The shipcaptaincrew.queenofsandiego.com guest photo system required pulling Instagram media alongside user-uploaded charter photos. This post covers the infrastructure decisions, API integration patterns, and AWS Lambda configuration needed to bridge Facebook's Instagram Graph API with a serverless photo gallery.

What Was Done

We enabled Instagram Graph API integration in an existing AWS Lambda function to fetch @sailjada Instagram posts filtered by event date and time window. The system now:

  • Accepts guest-uploaded photo submissions via HTTP POST
  • Queries approved guest photos from DynamoDB by event ID (format: 2026-04-29)
  • Fetches matching Instagram posts from Graph API using stored credentials
  • Combines both datasets and returns merged JSON to the frontend
  • Renders a unified photo gallery at /g/{event_id} via CloudFront

Technical Details: Lambda Function Architecture

The Lambda function is located at /Users/cb/Documents/repos/sites/queenofsandiego.com/tools/shipcaptaincrew/lambda_function.py (deployed as shipcaptaincrew in us-east-1, account 782785212866).

Key function structure:

  • handler(event, context) — Routes HTTP requests by path and method
  • get_guest_photos(event_id) — Queries DynamoDB table for approved photos
  • get_instagram_posts(event_id, window_hours=4) — Calls Graph API for media matching date/time
  • merge_photos(guest_photos, instagram_photos) — Combines results with metadata

The Instagram integration reads two environment variables:

  • IG_USER_ID — The Business Account ID for @sailjada (17.digit format)
  • IG_ACCESS_TOKEN — Long-lived token (valid 60 days) for Graph API calls

When either variable is missing, the function returns an empty Instagram array and logs a debug message. This "graceful degradation" allows the guest photo system to function independently while Instagram integration is being configured.

Infrastructure: AWS Components

Lambda Configuration:

  • Function name: shipcaptaincrew
  • Runtime: Python 3.11
  • Execution role: allows DynamoDB GetItem, Query, PutItem permissions
  • Environment variables: IG_USER_ID, IG_ACCESS_TOKEN, DYNAMODB_TABLE
  • Memory: 256 MB (sufficient for JSON API responses)
  • Timeout: 30 seconds (Graph API calls typically <2s)

DynamoDB Table:

  • Table name: shipcaptaincrew-guest-photos
  • Partition key: event_id (string, e.g., 2026-04-29)
  • Sort key: photo_id (string, UUIDv4)
  • Attributes: status (approved/pending), timestamp, uploader_email, photo_url
  • TTL enabled on ttl_expiration for auto-cleanup of pending submissions after 90 days

API Gateway & CloudFront:

  • Lambda integrated via HTTP API (not REST API—lower latency, lower cost)
  • CloudFront distribution: d[...].cloudfront.net with origin pointing to API endpoint
  • Route 53 alias: shipcaptaincrew.queenofsandiego.com → CloudFront distribution
  • Cache behavior: 60-second TTL for /g/* paths, 300-second for static assets

Facebook/Instagram App Setup

The Instagram Graph API credentials are managed through the sailjada-social Facebook app in the developer console.

Why we chose Graph API over Basic Display:

  • Graph API provides media metadata (caption, likes, timestamps) needed for filtering by date window
  • Basic Display only returns image URLs with no temporal data
  • Messaging API doesn't grant instagram_basic scope—it's for DMs, not media reads

Token acquisition workflow:

  • Short-lived token (1 hour) generated via Graph API Explorer with scopes: instagram_basic, pages_show_list
  • Exchanged for long-lived token (60 days) using app credentials
  • Stored as Lambda environment variable IG_ACCESS_TOKEN
  • Refresh strategy: monthly manual update or EventBridge-triggered Lambda for automation

API Endpoint Specification

GET /g/{event_id}

Returns combined photo gallery for a specific event date.

curl -s https://shipcaptaincrew.queenofsandiego.com/g/2026-04-29 \
  | jq '.guest_photos, .instagram_posts'

Response schema:

{
  "event_id": "2026-04-29",
  "guest_photos": [
    {
      "photo_id": "uuid-...",
      "url": "s3://bucket/.../photo.jpg",
      "uploader": "guest@example.com",
      "timestamp": "2026-04-29T14:32:00Z",
      "status": "approved"
    }
  ],
  "instagram_posts": [
    {
      "id": "18...",
      "caption": "Charter day photo",
      "media_type": "IMAGE",
      "timestamp": "2026-04-29T14:15:00Z",
      "media_url": "https://instagram.com/..."
    }
  ]
}

POST /upload

Accepts multipart form-data with photo and metadata. Returns photo_id and status (pending approval).

Key Decisions & Rationale

  • Serverless architecture: Lambda + API Gateway eliminates server management; CloudFront caching reduces API calls by 95% for repeat visitors
  • DynamoDB over RDS: Partition by event_id enables direct lookups; no JOIN complexity for a single-event query pattern
  • 60-day token window: Balances security (short-lived) with operational overhead (monthly refresh vs. daily)
  • Graceful degradation: Missing IG credentials don't break