Integrating Instagram Graph API into a Lambda-Based Photo Gallery: Architecture and Implementation

What Was Done

We enabled Instagram media integration for the guest photo gallery system at shipcaptaincrew.queenofsandiego.com/g/{event_id} by connecting the Instagram Graph API to the existing Lambda function. The system now fetches @sailjada Instagram posts from specific date/time windows and displays them alongside guest-uploaded photos. Previously, the integration was dormant, returning empty arrays due to missing credentials. This post walks through the exact steps, architecture decisions, and infrastructure changes required to activate this feature.

Technical Context and Architecture

The photo gallery system is built on a serverless architecture:

  • Lambda Function: shipcaptaincrew in region us-east-1, AWS account 782785212866
  • Route: /g/{event_id} — event_id is a date string like 2026-04-29
  • Data Sources: Guest-uploaded photos (stored in S3, metadata in DynamoDB) + Instagram posts via Graph API
  • Integration Pattern: Synchronous API calls from Lambda at request time, with results cached where possible

The Instagram Graph API integration uses a token-based authentication model. The architecture required three key components:

  • An Instagram App (Facebook Developer App) configured with Graph API product
  • A long-lived access token (valid for 60 days) stored as Lambda environment variables
  • A monthly refresh strategy to maintain token validity

Step 1: Configure the Instagram Graph API Product

The initial blocker was product misconfiguration. The app dashboard had "Messaging" added as a use case, which grants Direct Message scopes but not media-reading scopes. This is a critical distinction in Facebook's product model:

  • Messaging Product: Designed for sending/receiving DMs — wrong use case
  • Instagram Graph API Product: Designed for reading media, insights, and business data — correct use case

Action: In the Facebook Developer Dashboard, navigate to sailjada-social app → left sidebar → Add Product → search for Instagram → select Instagram Graph API (not Basic Display). This registers the app for media-reading scopes and creates the foundation for token generation.

Step 2: Connect the Instagram Business Account

Before requesting tokens, the Instagram account (@sailjada) must be connected to a Facebook Page and upgraded to a Business or Creator account. This is a prerequisite because the Instagram Graph API works through business accounts, not personal ones.

Verification: Confirm that @sailjada is linked to a Facebook Page you control. If not, this must be completed in the Instagram app settings before proceeding.

Step 3: Generate Short-Lived Access Token

Access token generation uses the Graph API Explorer, which is the quickest way to obtain a token with the correct scopes:

  • Navigate to developers.facebook.com/tools/explorer
  • Select app: sailjada-social from the dropdown
  • Click Generate Access Token
  • Select the Facebook Page linked to @sailjada
  • Confirm scopes include: instagram_basic, pages_show_list

The resulting token is short-lived (valid for ~2 hours) and is used only to fetch your Instagram Business User ID. Do not use this directly in Lambda.

Step 4: Retrieve Instagram Business User ID

With the short-lived token, make two API calls to obtain the Instagram User ID:

# First call: Get the page ID
curl -X GET "https://graph.instagram.com/v18.0/me?fields=id&access_token={SHORT_LIVED_TOKEN}"

# Response includes: "id": "{PAGE_ID}"

# Second call: Get the Instagram Business Account ID
curl -X GET "https://graph.instagram.com/v18.0/{PAGE_ID}?fields=instagram_business_account&access_token={SHORT_LIVED_TOKEN}"

# Response includes: "instagram_business_account": { "id": "{IG_USER_ID}" }

Store IG_USER_ID — this value is needed for subsequent media fetch calls and does not expire.

Step 5: Exchange for Long-Lived Token

The short-lived token cannot be used in production. Exchange it for a long-lived token (valid 60 days) using your app credentials:

curl -X GET "https://graph.instagram.com/v18.0/oauth/access_token?grant_type=ig_refresh_token&access_token={SHORT_LIVED_TOKEN}&client_id={APP_ID}&client_secret={APP_SECRET}"

# Response: { "access_token": "{LONG_LIVED_TOKEN}", "token_type": "bearer" }

Why this exchange? Long-lived tokens allow Lambda to fetch Instagram media without user interaction. Short-lived tokens require browser-based OAuth flows, which don't fit a server-side Lambda context.

Step 6: Configure Lambda Environment Variables

Update the Lambda function configuration to include the two credentials:

aws lambda update-function-configuration \
  --function-name shipcaptaincrew \
  --region us-east-1 \
  --environment Variables="{IG_USER_ID=17841400XXX,IG_ACCESS_TOKEN=IGAAxxxxxxxxxxx...}"

These environment variables are read by the Lambda handler. When both are present, the dormant Instagram integration code activates and begins fetching posts for the requested date/time window.

Key Architectural Decisions

  • Synchronous Fetching: Instagram posts are fetched at request time, not cached in DynamoDB. This keeps the gallery fresh but adds ~200-500ms latency per request. Alternative: Pre-fetch via EventBridge + SNS scheduled job to populate a cache.
  • 60-Day Token Lifecycle: Long-lived tokens expire after 60 days. A monthly refresh via Lambda scheduled rule prevents token expiration and requires minimal overhead (single curl call, update environment variables).
  • Scope Minimalism: Only instagram_basic and pages_show_list are requested. This follows the principle of least privilege — the app does not request insights, advertising, or other unnecessary scopes.

Verification and Testing

After deploying the Lambda configuration, navigate to shipcaptaincrew.queenofsandiego.com/g/2026-04-29 (or any date with posts) and verify that Instagram media appears alongside guest photos. Check CloudWatch Logs for the Lambda function to confirm API calls succeeded and no auth errors occurred.

What's Next

  • Implement automated token refresh: Create an EventBridge rule that triggers a Lambda function every 30 days to refresh the long-lived token.
  • Add caching layer: Consider caching Instagram posts in DynamoDB with