Skip to main content

Quick reference

FieldValue
Caption limit3,000 characters
Images per post1–20
Videos per post1
PDF documents per post1
Mixed media❌ Image OR video, never both
Image formatsJPG, PNG, GIF, WebP, HEIC, HEIF
Video formatsMP4, MOV, AVI, WebM
Document formatPDF
Max image / video size5 GB
Max document size100 MB
Post typesPersonal, Company, PDF Carousel
First comment✅ Auto-posts after publish
Account typesLinkedIn Personal + Company Pages

Before you start

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 and LinkedIn Company.
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 at the bottom is equivalent and accepted by the same endpoint.
SDK
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_…" }],
});
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",
    },
  }],
});
For details on mediaItems vs. pre-uploaded mediaIds, see 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. Send 2–20 images. Per-image alt text is supported via altText on each mediaItem.
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,
    },
  }],
});
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).
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.
PDFs only work on LinkedIn. Cross-posting a type: "document" item to any non-LinkedIn target returns 400. Send PDFs to LinkedIn accounts only.
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.
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

PropertyRequirement
Images per post1–20
FormatsJPG, PNG, GIF, WebP, HEIC, HEIF
Max file size5 GB
Aspect ratioAny (LinkedIn auto-crops to 1.91:1 in Feed)
Per-image alt text✅ via altText on each mediaItem
Max alt-text length1,000 chars

Videos

PropertyRequirement
Videos per post1
FormatsMP4, MOV, AVI, WebM
Max file size5 GB
Aspect ratio16:9 to 1:2.4
CodecsH.264 + AAC
Max resolution4K

Documents

PropertyRequirement
Documents per post1
FormatPDF
Max file size100 MB

Platform-specific fields

See platform settings for the canonical schema reference.

Personal — #linkedin-personal

FieldTypeRequiredDescription
platform"LINKEDIN_PERSON"YesDiscriminator.
visibility"PUBLIC" | "CONNECTIONS"No (default PUBLIC)Connections-only is honored by LinkedIn’s audience filter.
postAsPdfCarouselbooleanNoWhen true and 2+ images attached, composites them into a PDF and posts as a Document carousel.
pdfCarouselTitlestring (≤ 100)When postAsPdfCarousel: trueTitle shown under the carousel.

Company — #linkedin-company

FieldTypeRequiredDescription
platform"LINKEDIN_COMPANY"YesDiscriminator.
visibility"PUBLIC" | "CONNECTIONS"No (default PUBLIC)Kept for shape parity. Company actors always post publicly — server-forced.
postAsPdfCarouselbooleanNoSame as Personal.
pdfCarouselTitlestring (≤ 100)NoSame 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.
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

MetricAvailable
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

ErrorMeaningFix
LI_FIRST_COMMENT_THROTTLEDHit the 1-minute per-member create limitUse the retry button in the post-detail dialog.
LI_FIRST_COMMENT_SCOPE_MISSINGAccount doesn’t have w_member_social_feed (or w_organization_social_feed)Reconnect the account.
LI_MIXED_MEDIAImage + video in the same postLinkedIn requires one or the other.
LI_PDF_VIDEO_CONFLICTpostAsPdfCarousel: true with a video attachedRemove the video or set to false.
LI_PDF_NEEDS_TWO_IMAGESpostAsPdfCarousel: true with only one imageAdd at least one more image.
LI_MULTI_IMAGE_RATE_LIMITEDBurst uploaded >5 images/sec to the Images APIPostbreeze retries with exponential backoff.
LI_TOKEN_EXPIREDLinkedIn refresh failedReconnect.
LI_CAPTION_TOO_LONG> 3,000 charsTrim.

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.
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",
    },
  }],
});