```html

Integrating Instagram Graph API into a Lambda-Based Photo Gallery: Setup, Token Exchange, and Long-Lived Access

We recently enabled Instagram media integration in the guest photo gallery system at shipcaptaincrew.queenofsandiego.com/g/{event_id}. The Lambda function responsible for rendering this page (deployed as shipcaptaincrew in us-east-1, account 782785212866) had dormant Instagram integration code that required proper Graph API credentials and token management. This post walks through the exact steps we took to activate it, the architectural decisions made, and the token refresh strategy implemented.

What Was Done

The guest photo page aggregates two content sources: user-uploaded photos (stored in S3 and approved via a moderation workflow) and real-time posts from the @sailjada Instagram account during the same event window. Previously, the Instagram integration returned an empty array because the Lambda environment lacked the required IG_USER_ID and IG_ACCESS_TOKEN variables. We completed the full Instagram Graph API onboarding flow, generated credentials, and deployed them to the Lambda function configuration.

Technical Details: Instagram Graph API Setup

Step 1: Add the Correct Product

The critical first mistake to avoid: the app already had an Instagram product added—but it was configured for Messaging, which handles direct message flows, not media reads. This product does not grant the instagram_basic scope needed to fetch media data.

  • Navigate to developers.facebook.com/apps
  • Select the sailjada-social app
  • Click Add Product in the left sidebar
  • Search for and select Instagram Graph API (distinct from "Instagram Basic Display" and "Instagram Messaging")
  • Complete the setup flow—this adds the product to your sidebar

The Graph API product is the correct choice because it provides read access to media metadata, likes, comments, and insights for Business and Creator accounts. Our use case requires listing recent media from @sailjada's account, which is a Graph API operation.

Step 2: Verify Account Type and Link the Instagram Account

Instagram Graph API requires that the account being queried is either a Business Account or Creator Account. Personal accounts cannot be queried via the Graph API. We verified that @sailjada was already converted to a Creator account and confirmed it was linked to a Facebook Page (necessary for the token exchange flow).

Inside the Instagram Graph API section of the app dashboard, we used the API setup with Instagram login option to connect @sailjada by logging in with its credentials. This establishes the relationship between the app and the account.

Step 3: Generate a Short-Lived Access Token

Access tokens in the Instagram Graph API come in two forms: short-lived (valid for ~1 hour) and long-lived (valid for ~60 days). The short-lived token is the entry point to the system.

  • Open developers.facebook.com/tools/explorer
  • In the dropdown at the top, select the sailjada-social app
  • Click Generate Access Token
  • Select the Facebook Page linked to @sailjada
  • Grant the scopes: instagram_basic and pages_show_list
  • Copy the resulting token (this is a short-lived token, valid ~1 hour)

We kept this token temporarily in a secure location for the next steps. It is not stored long-term because short-lived tokens expire quickly and cannot be refreshed directly—instead, they are exchanged for long-lived tokens.

Step 4: Retrieve the IG_USER_ID

The Instagram User ID (business account ID) is required to query media. We obtained it using two API calls with the short-lived token:

curl -s "https://graph.instagram.com/me/accounts?access_token=SHORT_LIVED_TOKEN" \
  | jq '.data[] | select(.name == "YourPageName") | .id'

This returns the Facebook Page ID. Then, using that Page ID:

curl -s "https://graph.instagram.com/PAGE_ID?fields=instagram_business_account&access_token=SHORT_LIVED_TOKEN" \
  | jq '.instagram_business_account.id'

The id field in the instagram_business_account object is the IG_USER_ID. This ID is stable and does not change; it identifies the Instagram account you are querying.

Step 5: Exchange Short-Lived Token for Long-Lived Token

The short-lived token is exchanged for a long-lived token (valid ~60 days) using the app's credentials. This is a server-side operation that requires the app secret (not exposed to the client).

curl -s "https://graph.instagram.com/access_token" \
  -d "grant_type=ig_refresh_token" \
  -d "access_token=SHORT_LIVED_TOKEN" \
  | jq '.access_token'

The returned access_token is the long-lived token and becomes the IG_ACCESS_TOKEN environment variable. This token is now ready to be deployed.

Infrastructure: Lambda Environment Configuration

With the IG_USER_ID and IG_ACCESS_TOKEN in hand, we deployed them to the Lambda function:

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

These environment variables are now accessible within the Lambda function code. The function (written in Python) reads them on invocation:

import os
ig_user_id = os.environ.get('IG_USER_ID')
ig_access_token = os.environ.get('IG_ACCESS_TOKEN')

When both variables are present, the function's Instagram media fetch logic becomes active. The page now displays Instagram posts alongside user-uploaded photos.

Key Decisions

  • Long-Lived Over Short-Lived Tokens: Short-lived tokens expire hourly and cannot be directly refreshed. Long-lived tokens (60-day window) reduce operational complexity and eliminate the need for frequent token rotation during routine operation.
  • Token Storage in Lambda Environment: We store the token as a Lambda environment variable rather than in AWS Secrets Manager. Given that the token must be refreshed every 60 days and that the function is not highly security-sensitive (