Skip to main content

Instagram

This guide explains our Instagram integration: scopes, token exchanges, data persistence, and media publishing.

Quick links: FrontendBackend

Goals and constraints

  • Multi-account support: Users can connect multiple Instagram accounts
  • Use long‑lived tokens to minimize re‑auth.
  • Support image and reel publishing via the Instagram Graph API.

Why we do it this way

  • Instagram’s OAuth issues a short‑lived token which must be exchanged for a long‑lived token. We persist the long‑lived token for stability.
  • The Graph API requires igUserId to create and publish media. We capture it at auth time from the short‑lived token exchange.

Permissions and scopes

Default scopes requested:

  • instagram_business_basic
  • instagram_business_content_publish

These enable basic account info retrieval and media publishing (images and reels).

Backend

See also: Frontend

Token flow

  1. Start: Build Instagram OAuth URL with instagram_business_basic instagram_business_content_publish and state. Persist { uid, provider: 'instagram', returnTo } in oauthStates.
  2. Callback: Exchange code for short‑lived token via https://api.instagram.com/oauth/access_token.
  3. Exchange short‑lived for long‑lived token via https://graph.instagram.com/access_token with ig_exchange_token.
  4. Fetch profile for UI via GET me?fields=id,username,account_type,media_count,profile_picture_url using the long‑lived token.
  5. Persist to users/{uid}/integrations/instagram/{accountId} (where accountId = igUserId):
    • accessToken, tokenType, expiresIn, expiresAt, updatedAt
    • profile with platformId, displayName/username, avatarUrl, accountType
    • extra.igUserId from the short‑lived exchange

Multi-Account Support

Users can connect multiple Instagram accounts. Each account is stored separately with a unique accountId.

Storage path: users/{uid}/integrations/instagram/{accountId}

Where accountId is the Instagram user ID (igUserId).

Account selection:

  • If user has one account: Automatically selected, no accountId required in API requests
  • If user has multiple accounts: accountId is required in API requests (e.g., /v1/posts)
  • Missing required accountId returns 400 error with code missing_account

Data model

Firestore path: users/{uid}/integrations/instagram/{accountId}

Fields:

  • accountId: Unique identifier for this Instagram account (same as extra.igUserId)
  • accessToken, tokenType, expiresIn, expiresAt, updatedAt
  • profile: platformId, displayName, username, avatarUrl, accountType
  • extra.igUserId: numeric IG user id used for publishing
  • enabled_for_repost: (optional) boolean flag for automatic reposting

Publishing

Endpoint: Cloud Function publishInstagram (via orchestrator)

Flow:

  1. Resolve account: Determine accountId from request or auto-select if only one account exists
  2. Load users/{uid}/integrations/instagram/{accountId}.
  3. Validate extra.igUserId and accessToken.
  4. Create media container:
    • Image: POST /{igUserId}/media with image_url, optional caption.
    • Video/Reel: POST with video_url and media_type=REELS. Poll status until FINISHED.
  5. Publish container: POST /{igUserId}/media_publish?creation_id={container.id}.
  6. Return { ok: true, id }.

Common errors:

  • Missing igUserId: reconnect Instagram.
  • Invalid media URL or unsupported mime type.

Observability

  • Errors are surfaced in responses; detailed failures in server logs.

Security posture

  • Tokens and ids are stored per user; no cross‑tenant access.
  • We do not store unnecessary profile information.

Frontend

See also: Backend

Hooks and services:

  • apps/web/src/features/integrations/hooks/useConnectIntegration.ts
    • Starts OAuth by calling startAuth('instagram', idToken, returnTo, isLocal).
  • apps/web/src/features/integrations/hooks/usePublishInstagram.ts
    • Triggers photo/reel publishing via publishInstagram(functionsBase, idToken, imageUrl, caption?).
  • apps/web/src/features/integrations/services/publish.ts
    • Implements POST {functionsBase}/igPublish with { imageUrl, caption? }.

UI touchpoints:

  • apps/web/src/features/integrations/components/PlatformPicker.tsx renders the connected IG profile using integrations.instagram.profile.
  • apps/web/src/app/settings/page.tsx initiates connect via the hook.

Request/response shapes:

  • Publish: { imageUrl: string, caption?: string }{ ok: true, id }.

Error handling:

  • The publish hook throws HTTP text on errors; ensure imageUrl is provided.