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

# LinkedIn

> Schedule LinkedIn Personal and Company posts — single image, multi-image gallery, video, and PDF carousel.

## Quick reference

| Field                  | Value                             |
| ---------------------- | --------------------------------- |
| Caption limit          | 3,000 characters                  |
| Images per post        | 1–20                              |
| Videos per post        | 1                                 |
| PDF documents per post | 1                                 |
| Mixed media            | ❌ Image OR video, never both      |
| Image formats          | JPG, PNG, GIF, WebP, HEIC, HEIF   |
| Video formats          | MP4, MOV, AVI, WebM               |
| Document format        | PDF                               |
| Max image / video size | 5 GB                              |
| Max document size      | 100 MB                            |
| Post types             | Personal, Company, PDF Carousel   |
| First comment          | ✅ Auto-posts after publish        |
| Account types          | LinkedIn Personal + Company Pages |

## Before you start

<Warning>
  LinkedIn ships **two distinct multi-image experiences** with very different reach. Postbreeze exposes both:

  * **Photo gallery (MultiImage)** — 2–20 images in a swipeable gallery. Lighter feed treatment.
  * **Document carousel (PDF)** — your images composited into a single PDF that LinkedIn renders as a full-bleed slide deck. Typically **1.5–3× the organic reach** of MultiImage posts.

  Pick via `postAsPdfCarousel: true | false` on the platform options. See [platform settings → LinkedIn Personal](/concepts/platform-settings#linkedin-personal) and [LinkedIn Company](/concepts/platform-settings#linkedin-company).
</Warning>

Required scopes:

* **Personal**: `openid`, `profile`, `email`, `w_member_social`, `w_member_social_feed`.
* **Company**: `w_organization_social`, `r_organization_social`, `rw_organization_admin`, `w_organization_social_feed`.

The `_feed` scopes were added in May 2026 for first-comment support. Accounts connected before then need to reconnect — the compose UI shows a banner.

For analytics, additional scopes may be required and gated behind LinkedIn's Marketing Developer Platform review (multi-week process). Posting works without them.

## Quick start

Workspace is inferred from your API key — no `workspaceId` argument.
All examples use the **flat shape** (`content` + `platforms`); the
[nested shape](#full-control-nested-shape) at the bottom is equivalent
and accepted by the same endpoint.

```ts SDK theme={null}
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY! });

await postbreeze.posts.create({
  content: "Reflections on our first year — what I'd do differently.",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [{ type: "image", url: "https://cdn.example.com/photo.jpg" }],
  platforms: [{ accountId: "soc_linkedin_…" }],
});
```

<CodeGroup>
  ```js Node.js theme={null}
  import Postbreeze from "@postbreeze/node";

  const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY });

  const post = await postbreeze.posts.create({
    content: "Reflections on our first year — what I'd do differently.",
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    mediaItems: [{ type: "image", url: "https://cdn.example.com/photo.jpg" }],
    platforms: [{
      accountId: "soc_linkedin_…",
      platformOptions: {
        platform: "LINKEDIN_PERSON",
        visibility: "PUBLIC",
      },
    }],
  });
  ```

  ```python Python theme={null}
  import os
  from datetime import datetime, timedelta, timezone
  import requests

  API = "https://api.postbreeze.ai/api/v1"

  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "Reflections on our first year — what I'd do differently.",
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "mediaItems": [{"type": "image", "url": "https://cdn.example.com/photo.jpg"}],
          "platforms": [{
              "accountId": "soc_linkedin_…",
              "platformOptions": {
                  "platform": "LINKEDIN_PERSON",
                  "visibility": "PUBLIC",
              },
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "Reflections on our first year — what I would do differently.",
      "scheduledFor": "2026-06-02T12:00:30Z",
      "mediaItems": [
        { "type": "image", "url": "https://cdn.example.com/photo.jpg" }
      ],
      "platforms": [{
        "accountId": "soc_linkedin_…",
        "platformOptions": { "platform": "LINKEDIN_PERSON", "visibility": "PUBLIC" }
      }]
    }'
  ```
</CodeGroup>

For details on `mediaItems` vs. pre-uploaded `mediaIds`, see [media uploads](/concepts/media-uploads).

## Content types

### Personal post

`platform: "LINKEDIN_PERSON"`. Works for text-only, single image, single video, MultiImage gallery (2–20 images), PDF document, or PDF carousel (composited from images).

### Company page post

`platform: "LINKEDIN_COMPANY"`. Same supported shapes as Personal. The connected account must be on the workspace whose Company Page the user administers — the OAuth flow fans out one row per Page the user can post to. Company actors always render publicly server-side regardless of `visibility`.

### MultiImage gallery (2–20 images)

Send 2–20 images. Per-image alt text is supported via `altText` on each `mediaItem`.

<CodeGroup>
  ```js Node.js theme={null}
  const post = await postbreeze.posts.create({
    content: "5 patterns I see in fast-growing B2B teams 🧵",
    scheduledFor: new Date(Date.now() + 30_000).toISOString(),
    mediaItems: [
      { type: "image", url: "https://cdn.example.com/cover.jpg", altText: "Cover slide titled '5 patterns'" },
      { type: "image", url: "https://cdn.example.com/p1.jpg", altText: "Pattern 1 — They write internal memos" },
      { type: "image", url: "https://cdn.example.com/p2.jpg" },
      { type: "image", url: "https://cdn.example.com/p3.jpg" },
      { type: "image", url: "https://cdn.example.com/p4.jpg" },
    ],
    platforms: [{
      accountId: "soc_linkedin_…",
      platformOptions: {
        platform: "LINKEDIN_PERSON",
        visibility: "PUBLIC",
        postAsPdfCarousel: false,
      },
    }],
  });
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{API}/posts",
      headers={"Authorization": f"Bearer {os.environ['POSTBREEZE_API_KEY']}"},
      json={
          "content": "5 patterns I see in fast-growing B2B teams 🧵",
          "scheduledFor": (datetime.now(timezone.utc) + timedelta(seconds=30)).isoformat(),
          "mediaItems": [
              {"type": "image", "url": "https://cdn.example.com/cover.jpg", "altText": "Cover slide titled '5 patterns'"},
              {"type": "image", "url": "https://cdn.example.com/p1.jpg", "altText": "Pattern 1 — They write internal memos"},
              {"type": "image", "url": "https://cdn.example.com/p2.jpg"},
              {"type": "image", "url": "https://cdn.example.com/p3.jpg"},
              {"type": "image", "url": "https://cdn.example.com/p4.jpg"},
          ],
          "platforms": [{
              "accountId": "soc_linkedin_…",
              "platformOptions": {
                  "platform": "LINKEDIN_PERSON",
                  "visibility": "PUBLIC",
                  "postAsPdfCarousel": False,
              },
          }],
      },
  )
  res.raise_for_status()
  ```

  ```bash curl theme={null}
  curl -X POST https://api.postbreeze.ai/api/v1/posts \
    -H "Authorization: Bearer $POSTBREEZE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "content": "5 patterns I see in fast-growing B2B teams 🧵",
      "scheduledFor": "2026-06-02T12:00:30Z",
      "mediaItems": [
        { "type": "image", "url": "https://cdn.example.com/cover.jpg", "altText": "Cover slide titled 5 patterns" },
        { "type": "image", "url": "https://cdn.example.com/p1.jpg", "altText": "Pattern 1 — They write internal memos" },
        { "type": "image", "url": "https://cdn.example.com/p2.jpg" },
        { "type": "image", "url": "https://cdn.example.com/p3.jpg" },
        { "type": "image", "url": "https://cdn.example.com/p4.jpg" }
      ],
      "platforms": [{
        "accountId": "soc_linkedin_…",
        "platformOptions": {
          "platform": "LINKEDIN_PERSON",
          "visibility": "PUBLIC",
          "postAsPdfCarousel": false
        }
      }]
    }'
  ```
</CodeGroup>

### Document carousel (composited from images)

Send 2+ images and `postAsPdfCarousel: true`. Postbreeze composites the images into a single PDF and uploads as a document — LinkedIn renders it as a swipeable slide deck. `pdfCarouselTitle` is required and shows under the carousel (≤ 100 chars).

```ts theme={null}
await postbreeze.posts.create({
  content: "5 patterns I see in fast-growing B2B teams 🧵",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "image", url: "https://cdn.example.com/slide1.jpg" },
    { type: "image", url: "https://cdn.example.com/slide2.jpg" },
    { type: "image", url: "https://cdn.example.com/slide3.jpg" },
  ],
  platforms: [{
    accountId: "soc_linkedin_…",
    platformOptions: {
      platform: "LINKEDIN_PERSON",
      visibility: "PUBLIC",
      postAsPdfCarousel: true,
      pdfCarouselTitle: "5 patterns I see",
    },
  }],
});
```

### Real PDF document

Attach an actual PDF — LinkedIn renders it as a swipeable carousel natively. This is **separate from** `postAsPdfCarousel` (which composites images into a synthetic PDF); here you're uploading a real document. Use `type: "document"` in `mediaItems`, or pass a `med_…` id from a presigned PDF upload via `mediaIds`.

<Warning>
  PDFs **only work on LinkedIn**. Cross-posting a `type: "document"` item to any non-LinkedIn target returns 400. Send PDFs to LinkedIn accounts only.
</Warning>

```ts theme={null}
await postbreeze.posts.create({
  content: "Our 2026 product strategy in one deck.",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [
    { type: "document", url: "https://cdn.example.com/strategy-deck.pdf" },
  ],
  platforms: [{
    accountId: "soc_linkedin_…",
    platformOptions: { platform: "LINKEDIN_PERSON", visibility: "PUBLIC" },
  }],
});
```

### Video

`platform: "LINKEDIN_PERSON"` (or `LINKEDIN_COMPANY`) with a single video `mediaItem`.

```ts theme={null}
await postbreeze.posts.create({
  content: "Walking through our new onboarding flow.",
  scheduledFor: new Date(Date.now() + 30_000).toISOString(),
  mediaItems: [{ type: "video", url: "https://cdn.example.com/onboarding.mp4" }],
  platforms: [{
    accountId: "soc_linkedin_…",
    platformOptions: { platform: "LINKEDIN_PERSON", visibility: "PUBLIC" },
  }],
});
```

## Media requirements

### Images

| Property            | Requirement                                 |
| ------------------- | ------------------------------------------- |
| Images per post     | 1–20                                        |
| Formats             | JPG, PNG, GIF, WebP, HEIC, HEIF             |
| Max file size       | 5 GB                                        |
| Aspect ratio        | Any (LinkedIn auto-crops to 1.91:1 in Feed) |
| Per-image alt text  | ✅ via `altText` on each `mediaItem`         |
| Max alt-text length | 1,000 chars                                 |

### Videos

| Property        | Requirement         |
| --------------- | ------------------- |
| Videos per post | 1                   |
| Formats         | MP4, MOV, AVI, WebM |
| Max file size   | 5 GB                |
| Aspect ratio    | 16:9 to 1:2.4       |
| Codecs          | H.264 + AAC         |
| Max resolution  | 4K                  |

### Documents

| Property           | Requirement |
| ------------------ | ----------- |
| Documents per post | 1           |
| Format             | PDF         |
| Max file size      | 100 MB      |

## Platform-specific fields

See [platform settings](/concepts/media-uploads) for the canonical schema reference.

### Personal — [`#linkedin-personal`](/concepts/platform-settings#linkedin-personal)

| Field               | Type                          | Required                       | Description                                                                                    |
| ------------------- | ----------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------- |
| `platform`          | `"LINKEDIN_PERSON"`           | Yes                            | Discriminator.                                                                                 |
| `visibility`        | `"PUBLIC"` \| `"CONNECTIONS"` | No (default `PUBLIC`)          | Connections-only is honored by LinkedIn's audience filter.                                     |
| `postAsPdfCarousel` | boolean                       | No                             | When true and 2+ images attached, composites them into a PDF and posts as a Document carousel. |
| `pdfCarouselTitle`  | string (≤ 100)                | When `postAsPdfCarousel: true` | Title shown under the carousel.                                                                |

### Company — [`#linkedin-company`](/concepts/platform-settings#linkedin-company)

| Field               | Type                          | Required              | Description                                                                     |
| ------------------- | ----------------------------- | --------------------- | ------------------------------------------------------------------------------- |
| `platform`          | `"LINKEDIN_COMPANY"`          | Yes                   | Discriminator.                                                                  |
| `visibility`        | `"PUBLIC"` \| `"CONNECTIONS"` | No (default `PUBLIC`) | Kept for shape parity. Company actors **always** post publicly — server-forced. |
| `postAsPdfCarousel` | boolean                       | No                    | Same as Personal.                                                               |
| `pdfCarouselTitle`  | string (≤ 100)                | No                    | Same as Personal.                                                               |

## First comment

Pass `firstComment` on the platform entry. Postbreeze waits 8 seconds after the main post (LinkedIn enforces a per-member 1-minute throttle on create-actions) and then posts as the same author. Supported on both Personal and Company.

```ts theme={null}
platforms: [{
  accountId: "soc_linkedin_…",
  platformOptions: { platform: "LINKEDIN_PERSON", visibility: "PUBLIC" },
  firstComment: "Full breakdown: https://blog.example.com/patterns",
}]
```

Limit: 1,250 characters per LinkedIn comment (smaller than the 3,000 caption cap — surface this in your UI).

## Analytics

| Metric              | Available                                         |
| ------------------- | ------------------------------------------------- |
| Likes               | ✅                                                 |
| Comments            | ✅                                                 |
| Reshares            | ✅                                                 |
| Impressions         | ⚠️ Requires Marketing Developer Platform approval |
| Click-through rate  | ⚠️ Requires MDP approval                          |
| Reach (unique)      | ⚠️ Requires MDP approval                          |
| Engagement rate     | ⚠️ Requires MDP approval                          |
| Followers (Company) | ⚠️ Requires MDP approval                          |

The Postbreeze analytics page detects when these scopes are missing and shows an "approval pending" banner instead of fake zeros.

## Common errors

| Error                            | Meaning                                                                       | Fix                                             |
| -------------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- |
| `LI_FIRST_COMMENT_THROTTLED`     | Hit the 1-minute per-member create limit                                      | Use the retry button in the post-detail dialog. |
| `LI_FIRST_COMMENT_SCOPE_MISSING` | Account doesn't have `w_member_social_feed` (or `w_organization_social_feed`) | Reconnect the account.                          |
| `LI_MIXED_MEDIA`                 | Image + video in the same post                                                | LinkedIn requires one or the other.             |
| `LI_PDF_VIDEO_CONFLICT`          | `postAsPdfCarousel: true` with a video attached                               | Remove the video or set to false.               |
| `LI_PDF_NEEDS_TWO_IMAGES`        | `postAsPdfCarousel: true` with only one image                                 | Add at least one more image.                    |
| `LI_MULTI_IMAGE_RATE_LIMITED`    | Burst uploaded >5 images/sec to the Images API                                | Postbreeze retries with exponential backoff.    |
| `LI_TOKEN_EXPIRED`               | LinkedIn refresh failed                                                       | Reconnect.                                      |
| `LI_CAPTION_TOO_LONG`            | > 3,000 chars                                                                 | Trim.                                           |

## What you can't do

* ❌ Schedule via LinkedIn's native scheduler
* ❌ Mix image + video in one post
* ❌ Multi-video posts (1 video max)
* ❌ Image-in-first-comment (separate flow, deferred to v2)
* ❌ Tag specific users via API (you can include `@Name` in text but no structured tags)
* ❌ Polls or events
* ❌ Newsletters
* ❌ Edit a post after publish
* ❌ Stories (LinkedIn deprecated these)

## Full control: nested shape

The `targets` / `socialAccountId` / `caption` / `scheduledAt` / `mediaIds` shape is the original API surface and remains fully supported. Use whichever you prefer — both go to the same `POST /api/v1/posts` endpoint.

```ts theme={null}
import Postbreeze from "@postbreeze/node";

const postbreeze = new Postbreeze({ apiKey: process.env.POSTBREEZE_API_KEY! });

await postbreeze.posts.create({
  caption: "Reflections on our first year — what I'd do differently.",
  scheduledAt: new Date(Date.now() + 30_000).toISOString(),
  mediaIds: ["med_image_…"],
  targets: [{
    socialAccountId: "soc_linkedin_…",
    platformOptions: {
      platform: "LINKEDIN_PERSON",
      visibility: "PUBLIC",
    },
  }],
});
```
