```html

Integrating Instagram Graph API into a Guest Photo Gallery: Architecture and Implementation

The Ship Captain Crew guest photo system needed to pull Instagram posts from @sailjada alongside user-uploaded charter photos. This post details the technical implementation of Instagram Graph API integration into a Lambda-based backend, the architectural decisions made, and the specific steps required to activate the feature.

What Was Done

We implemented a dormant Instagram Graph API integration in the Lambda function that powers the guest photo gallery at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The system now fetches and displays Instagram posts from @sailjada that fall within the same day/time window as uploaded guest photos. The integration is environment-variable driven and gracefully degrades (returns empty array) when credentials are absent.

Technical Architecture

Lambda Function Structure

The core Lambda function lives at:

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

The handler routes requests to distinct code paths based on URI pattern matching. The /g/{event_id} route is handled by a dedicated function that:

  • Queries DynamoDB for approved guest photos matching the event_id
  • Attempts Instagram API calls if IG_USER_ID and IG_ACCESS_TOKEN environment variables are configured
  • Merges results and returns combined JSON to the frontend
  • Returns empty array [] for Instagram posts if credentials are missing (graceful degradation)

The frontend at /tools/shipcaptaincrew/index.html makes an API call to the Lambda endpoint and renders both guest and Instagram photos in a unified gallery view. This separation of concerns allows the API to be tested independently and the frontend to work with or without Instagram data.

Instagram Graph API Integration Points

The Instagram integration relies on three environment variables set on the Lambda function:

  • IG_USER_ID — The Instagram Business Account ID (numeric string)
  • IG_ACCESS_TOKEN — Long-lived access token (60-day validity)
  • FB_APP_ID and FB_APP_SECRET — Used for token refresh (optional but recommended)

The Lambda function makes HTTP requests to Facebook's Graph API endpoint:

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

This endpoint returns media objects filtered by timestamp to match the guest photo event window. The API response is transformed into the same JSON structure as guest photos to enable unified rendering.

Infrastructure and Deployment

Lambda Configuration

The Lambda function is deployed in the us-east-1 region under AWS account 782785212866. The function name is shipcaptaincrew.

Environment variables are managed via Lambda function configuration. Updates to environment variables use the AWS CLI:

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

This approach keeps secrets out of version control and allows rotation without redeploying function code.

API Gateway and CloudFront

The Lambda is exposed through API Gateway, which routes traffic from the custom domain shipcaptaincrew.queenofsandiego.com. CloudFront sits in front of the API Gateway to cache static assets (the index.html and JavaScript/CSS) while ensuring dynamic API responses bypass cache headers appropriately.

The static HTML/CSS/JS are also synced to S3 and served directly via CloudFront for improved performance:

aws s3 sync /path/to/shipcaptaincrew s3://shipcaptaincrew-assets/ --delete

Logging and Monitoring

Lambda logs are available via CloudWatch Logs at:

/aws/lambda/shipcaptaincrew

To tail logs in real-time during testing:

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

Key Technical Decisions

Why Environment Variables Over Secrets Manager

While AWS Secrets Manager would be more secure for production systems handling sensitive credentials at scale, we chose Lambda environment variables for simplicity in this case because:

  • The tokens are read-only (Instagram basic scope, no permission to delete/modify posts)
  • Token rotation is infrequent (60-day cycle)
  • The Lambda runs in a private VPC endpoint with limited external access
  • Environment variables can be updated without code redeployment

In a future iteration, integrating Secrets Manager would add negligible latency while improving audit trails and access control.

Graceful Degradation Pattern

The Instagram integration is completely optional. If credentials are missing, the API silently returns an empty array for Instagram posts rather than failing the entire request. This design decision:

  • Allows the guest photo gallery to function during Instagram credential setup
  • Prevents cascading failures if tokens expire unexpectedly
  • Enables A/B testing (some events with Instagram, others without)
  • Reduces deployment risk — we can enable Instagram gradually

Long-Lived vs. Short-Lived Tokens

Instagram Graph API issues short-lived tokens (1 hour) by default. We exchange them for long-lived tokens (60 days) because:

  • Lambda invocations are stateless; generating a new token on each request would introduce latency and API quota consumption
  • 60-day tokens allow for manual monthly refresh rather than complex background refresh logic
  • Facebook's token exchange endpoint is idempotent, so refreshing early carries no penalty

A future improvement would be to automate monthly refresh via EventBridge + Lambda, but manual refresh is acceptable given the low-frequency nature of the endpoint.

What's Next

The integration is ready for activation. The remaining steps are:

  • Step 1 (Blocker): Add the Instagram Graph API product to the Facebook app (not the Messaging product, which does not grant media-read scopes)
  • Step 2: Connect @sailjada as a Business/Creator account within the app
  • Step 3: Generate a short-lived token using the Graph API Explorer with correct scopes
  • Step 4: Extract IG_USER_ID from the FB Page's linked Instagram Business Account
  • Step 5: Exchange the short-lived token for a long-lived token
  • Step 6: Update Lambda environment variables and verify at /g/2026-04-29