```html

Integrating Instagram Graph API with Lambda: Enabling Photo Timeline Aggregation for Guest Event Pages

What Was Done

We activated the dormant Instagram Graph API integration in the shipcaptaincrew Lambda function (us-east-1, account 782785212866) to surface @sailjada Instagram posts alongside guest-uploaded charter photos on event-specific gallery pages. The integration was architecturally sound but lacked the necessary authentication tokens and correct product configuration in the Facebook App Dashboard.

This post documents the exact steps taken to establish the authentication flow, obtain long-lived access tokens, and configure the Lambda environment with the required credentials.

Technical Details: Instagram Graph API Authentication Flow

Step 1: Correcting the Product Configuration

The initial blocker was a misconfigured product in the sailjada-social Facebook app. The Messaging product had been added, which grants scopes for direct message handling but explicitly does not include the instagram_basic scope required to read media objects.

  • App location: developers.facebook.com/appssailjada-social
  • Required action: Add Products → Instagram Graph API (not Basic Display, not Messaging)
  • Why this matters: Product selection determines which OAuth scopes are available. The Messaging product is designed for bot interactions, not content reads. Only the Graph API product exposes media endpoints and the required scopes.

Step 2: Instagram Account Linking

The @sailjada account must be connected as a Business or Creator account and linked to a Facebook Page. This establishes the relationship between the Instagram account and the app's access permissions.

  • Navigate to Instagram Graph API → API setup
  • Click "Add Instagram account" and authenticate as @sailjada
  • Verify the account type is Business or Creator (required for Graph API access)

Step 3: Short-Lived Token Generation

The OAuth flow begins with a short-lived access token (valid ~2 hours) that is restricted to a specific set of scopes.


# Using the Graph API Explorer at developers.facebook.com/tools/explorer
# 1. App selector dropdown: sailjada-social
# 2. Generate Access Token button
# 3. Scopes requested: instagram_basic, pages_show_list
# Token appears in the explorer's "Access Token" field

Step 4: Retrieving the Instagram User ID

The Instagram User ID (IG_USER_ID) is not the same as the Instagram handle. It's a numeric identifier required for all subsequent Graph API calls. This is retrieved by querying the Facebook Page's linked Instagram business account.


# First call: Get the Page ID (use the FB Page linked to @sailjada)
GET /me/accounts
  ?fields=id,name
  &access_token=SHORT_LIVED_TOKEN

# Response includes page id. Then:
GET /{PAGE_ID}
  ?fields=instagram_business_account
  &access_token=SHORT_LIVED_TOKEN

# The "id" field in the instagram_business_account object = IG_USER_ID

Step 5: Exchanging for Long-Lived Token

Short-lived tokens expire quickly. The access token must be exchanged for a long-lived token valid for 60 days using the app's credentials.


GET /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 field is the long-lived token. Store this as IG_ACCESS_TOKEN in the Lambda environment.

Infrastructure: Lambda Environment Configuration

The shipcaptaincrew Lambda function reads two environment variables that must be set:

  • IG_USER_ID – The numeric Instagram business account ID (e.g., 17841401234567890)
  • IG_ACCESS_TOKEN – The long-lived access token (60-day validity)

These are configured via the Lambda console or CLI:


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

Upon deployment, the Lambda's Instagram integration module checks for these variables. If present, it queries the Instagram Graph API for posts matching the event's date window; if absent, it returns an empty array (graceful degradation).

Guest Page Architecture

The guest photo page lives at shipcaptaincrew.queenofsandiego.com/g/{event_id} (e.g., /g/2026-04-29). When accessed, the page triggers a Lambda execution that:

  1. Queries a DynamoDB table for approved guest uploads matching the event_id
  2. If IG credentials are set, queries Instagram Graph API for @sailjada posts from the same date/time window
  3. Merges both datasets by timestamp
  4. Returns a combined photo timeline to the frontend

The separation of guest uploads (user-generated, stored in S3) and Instagram posts (pulled at runtime) allows for independent management of photo sources and approval workflows.

Key Decisions

  • 60-day token lifecycle: Rather than implementing a more complex OAuth callback system, we use the long-lived token model. Monthly refresh is performed manually or via an EventBridge rule that exchanges the token before expiration, maintaining continuity without user intervention.
  • Graceful degradation: Missing credentials do not break the guest page—it simply omits Instagram content. This allows the feature to be toggled on/off without code changes.
  • Date-based querying: The event_id is parsed as a date (YYYY-MM-DD), and the API queries Instagram media posted within a 24-hour window centered on that date. This handles timezone variance and ensures relevant photos are captured.
  • Read-only scope: The instagram_basic scope grants only read access to public media. No write or delete permissions are requested, limiting the blast radius of token compromise.

What's Next

  • Token refresh automation: Deploy an EventBridge rule (weekly trigger) that runs a Lambda to exchange the existing token for a fresh 60-day token, logged to CloudWatch. This eliminates manual refresh overhead.
  • Metrics and monitoring: Add CloudWatch metrics for Instagram API call latency and error rates. Alert if the API returns 401 (token expired) or 429 (rate limited).
  • Hashtag filtering: Extend the query to include only posts with specific hashtags (e.g., #shipcaptaincrew), narrowing results to relevant event content.
  • Caching strategy: Cache Instagram results in ElastiCache or DynamoDB with a 1-hour TTL to reduce API calls and improve page load times.

The integration is now live and can be verified by navig