> ## Documentation Index
> Fetch the complete documentation index at: https://docs.letmepost.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Instagram

> Instagram Business / Creator via the Content Publishing API. Standalone Instagram Login — no Facebook required.

<Warning>
  **Before you start.** Instagram requires a **Business** or **Creator** account. Personal accounts cannot publish via the Content Publishing API regardless of scopes granted — the connect flow rejects them with `instagram.account_type.requires_professional` and a remediation pointing at the Switch to professional account toggle in the IG app.
</Warning>

## Quick reference

| Limit / capability      | Value                                                 |
| ----------------------- | ----------------------------------------------------- |
| Caption character limit | 2,200 graphemes                                       |
| Items per carousel      | 2 – 10 (mixed image + video allowed)                  |
| **Image format**        | **JPEG only** (PNG, WebP, HEIC, GIF rejected by Meta) |
| Image size              | 8,000,000 bytes (8 MB)                                |
| Video formats           | mp4, mov                                              |
| Video size              | 1,000,000,000 bytes (1 GB)                            |
| Reels duration          | ≤ 90 s (platform-enforced)                            |
| Alt text per item       | 2,200 graphemes                                       |
| Post types supported    | single image, single video (Reels), carousel          |
| Text-only post          | **Not supported** — every post requires media         |
| Scheduling              | Yes (via `scheduledAt`)                               |
| Reply / thread support  | Not supported in v1                                   |
| First comment           | Not supported in v1                                   |
| Story                   | Not supported in v1                                   |
| Inbox / DM              | Not supported in v1                                   |
| Analytics               | Not supported in v1                                   |

## Connect an account

Instagram has its own OAuth (since the 2024 Instagram Login product) — distinct from Facebook Login. There is no FB fan-out anymore; connecting Instagram does **not** also connect Facebook.

`POST /v1/accounts/connect/instagram` produces an authorize URL at `instagram.com/oauth/authorize`. After the user grants, the callback runs three Graph calls:

1. Exchange `code` → short-lived token (1 h).
2. Swap short-lived → long-lived (60 d).
3. `GET /me?fields=id,user_id,username,account_type` — the **Content Publishing API expects `id` (the IG-scoped user id), not `user_id`** — the provider persists `id` as `platformAccountId`. If `account_type === "PERSONAL"` the connect is rejected.

### Scopes

```
instagram_business_basic           — read /me + identity
instagram_business_content_publish — create + publish containers
```

`extended` scopes (off by default) cover comment + insight reads — not required for publishing.

### Token lifecycle

Long-lived tokens last 60 days and are refreshable any time after the first 24 hours via `GET /refresh_access_token`. The provider refreshes on schedule; `token.expiring` fires before expiry.

## Post types

### Single image

```json single-image.json theme={"system"}
{
  "targets": [{ "accountId": "..." }],
  "text": "Caption is optional",
  "media": [
    { "kind": "image", "mediaId": "med_…", "altText": "..." }
  ]
}
```

### Single video (Reels)

Single-video posts route through the `REELS` container — Instagram retired the legacy `VIDEO` product surface in 2024.

```json reels.json theme={"system"}
{
  "targets": [{ "accountId": "..." }],
  "text": "Reels caption",
  "media": [
    { "kind": "video", "mediaId": "med_…" }
  ]
}
```

### Carousel (2 – 10, mixed)

Carousel children can mix image and video items.

```json carousel.json theme={"system"}
{
  "targets": [{ "accountId": "..." }],
  "text": "Mixed carousel of three",
  "media": [
    { "kind": "image", "mediaId": "med_a…" },
    { "kind": "video", "mediaId": "med_b…" },
    { "kind": "image", "mediaId": "med_c…" }
  ]
}
```

The publisher creates each child container (`is_carousel_item=true`), polls each to `FINISHED`, then creates a `CAROUSEL` parent with the child ids and publishes the parent.

## Wisdom (platform-specific things that bite)

<Tip>
  * **JPEG only** for images. PNG, WebP, HEIC, and GIF will be rejected with `code 100, error_subcode 2207003`. Preflight catches this locally so you don't lose a roundtrip — re-encode to JPEG via your image pipeline (or upload via [`POST /v1/media`](/api-reference) which preserves the source mime).
  * Media URLs **must be publicly reachable** by Meta's CDN — anonymous HTTPS, no auth, no Drive links, no signed S3 URLs that expire before the container finishes. Unreachable URLs surface as `OAuthException 2207052`, the canonical opaque-rejection.
  * The first **125 characters** of the caption show before the "more" fold in feed. Front-load anything important.
  * Container expiry: Instagram's media containers live for **24 hours** after creation. The publisher creates + publishes in one synchronous flow so this rarely matters, but a scheduled post that ages out triggers `instagram.container.expired`.
  * The Content Publishing API uses `id` (the IG-scoped user id) on `/{ig-user-id}/media` — **not** `user_id` from the token-exchange response. The provider stores the right one; if you ever rebuild this manually, sending `user_id` returns `code:2`.
  * Reels duration is platform-enforced. We size-check at preflight, but the 90 s ceiling is checked by Instagram at container-finalize and surfaces as a platform rejection.
  * The publisher's response carries a permalink fetched best-effort via `GET /{media-id}?fields=permalink`. If that call 404s, the publish still succeeds and the response carries the id alone.
</Tip>

## Common errors

| Error rule                                                                                                | What it means                               | How to fix                                                                   |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------- |
| [`instagram.media.required`](/preflight/instagram-media-required)                                         | No media on the post                        | Attach at least one image or video; Instagram has no text-only feed surface. |
| [`instagram.text.max_graphemes`](/preflight/instagram-text-max_graphemes)                                 | Caption > 2,200 graphemes                   | Trim under 2,200.                                                            |
| [`instagram.media.count_max`](/preflight/instagram-media-count_max)                                       | More than 10 items in a carousel            | Reduce to ≤10, or split into multiple posts.                                 |
| [`instagram.media.mime_allowed`](/preflight/instagram-media-mime_allowed)                                 | Image isn't JPEG, or video isn't mp4/mov    | Re-encode to JPEG (images) or mp4/mov (video).                               |
| [`instagram.media.image_size_max`](/preflight/instagram-media-image_size_max)                             | Image > 8 MB                                | Compress under 8,000,000 bytes.                                              |
| [`instagram.media.video_size_max`](/preflight/instagram-media-video_size_max)                             | Video > 1 GB                                | Compress under 1,000,000,000 bytes.                                          |
| [`instagram.container.expired`](/preflight/instagram-container-expired)                                   | Container aged past Instagram's 24 h window | Re-create and publish in one flow.                                           |
| [`instagram.account_type.requires_professional`](/preflight/instagram-account_type-requires_professional) | Connect attempted with a PERSONAL account   | Switch the IG account to Business or Creator in-app, then reconnect.         |

## What you can't do (yet)

* Stories (Stories use a separate `STORIES` container type not in v1).
* Direct messages.
* First comment as the author.
* Tagging users or product tags.
* Editing or deleting a published post.
* Posting from a **Personal** Instagram account (Meta's API restriction — switch to Business or Creator).
* Reading comments, replies, or insights (requires `extended` scopes plus read endpoints not in v1).

## API reference

* [`POST /v1/posts`](/api-reference) — primary publish.
* [`POST /v1/media`](/api-reference) — upload images/videos for `mediaId` references.
* [`POST /v1/accounts/connect/instagram`](/api-reference) — start an Instagram OAuth flow.
