# LinkedIn API: Posting Reference

LinkedIn appears as two independent channels: one for a personal profile and one for a company page. A single workspace can connect both at the same time and post to each separately.

| Channel ID | Target |
|------------|--------|
| `linkedin` | Personal profile (the individual who connected the account) |
| `linkedin_page` | Company page (the organization whose page was selected during OAuth) |

Both channels share a single LinkedIn OAuth token under the hood. Reconnecting either refreshes credentials for both.

## Supported content types

| Type | `linkedin` (Profile) | `linkedin_page` (Page) |
|------|----------------------|------------------------|
| Feed post | ✅ | ✅ |
| Story | - | - |
| Reel | - | - |

LinkedIn does not support stories or reels through its API. Only feed posts work.

## Minimal example

**Posting to a personal profile:**

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Excited to share our latest update." },
    "accounts": ["your-linkedin-profile-account-id"]
  }'
```

**Posting to a company page:**

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "Company announcement." },
    "accounts": ["your-linkedin-page-account-id"]
  }'
```

**Posting the same content to both simultaneously:** include both account IDs in the `accounts` array. OmniSocials publishes to each independently.

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": {
      "default": "Same content on both",
      "linkedin": "Personal voice for the profile",
      "linkedin_page": "Official company announcement"
    },
    "accounts": [
      "your-linkedin-profile-account-id",
      "your-linkedin-page-account-id"
    ]
  }'
```

## Platform-specific options

Set a first comment under the channel's own key: `linkedin` for the profile, `linkedin_page` for the company page:

| Field | Type | Description |
|-------|------|-------------|
| `linkedin.first_comment` | string | Text auto-posted as the first comment on the profile post right after it publishes (max 1250 characters). Common for "link in first comment" to avoid the in-caption link reach penalty. |
| `linkedin_page.first_comment` | string | Same, for the company page post (max 1250 characters). |
| `linkedin.carousel_as_images` | boolean | `true` publishes 2 or more images as a plain multi-image gallery instead of the default document carousel. See [Media requirements](#media-requirements). |
| `linkedin.document_source` | string | `original_pdf` (default) or `slides`. When the images are the unchanged pages of one uploaded PDF, the default sends that original file as the document; `slides` rebuilds it from the slide images. See [Original PDF](#original-pdf). |
| `linkedin_page.carousel_as_images`, `linkedin_page.document_source` | | Same, for the company page post. Independent per channel. |
| `linkedin_poll` | object | Non-sponsored poll; see [Polls](#polls) below. |
| `video_cover` | object | Top-level. Video thumbnail for a video post, applied to both channels; use `video_cover.overrides.linkedin` or `.linkedin_page` to give one channel its own frame or image. See [Video covers](/creating-posts#video-covers). |

Aside from the optional first comment and poll, LinkedIn has no required option fields; the default create-post body works for both channels.

Video thumbnails go through LinkedIn's Videos API (`initializeUpload` with `uploadThumbnail`, then the image is uploaded before the video is finalized). A thumbnail that cannot be uploaded never fails the post; LinkedIn then picks its own frame.

## Polls

LinkedIn's Poll API lets you post a non-sponsored poll: a question with 2-4 answer options and a duration. Pass a top-level `linkedin_poll` object keyed by channel: `linkedin` (personal profile) and `linkedin_page` (company page) each carry their own **independent** poll, or none. `content` is still the post's caption, shown above the poll.

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "What should we build next?" },
    "accounts": ["your-linkedin-account-id"],
    "linkedin_poll": {
      "linkedin": {
        "question": "What should we build next?",
        "options": ["Mobile app", "Public API", "More integrations"],
        "duration": "SEVEN_DAYS"
      }
    }
  }'
```

| Field | Type | Description |
|-------|------|--------------|
| `linkedin_poll.linkedin` | object \| null | Poll for the personal profile post. Omit or null = not a poll. |
| `linkedin_poll.linkedin_page` | object \| null | Poll for the company page post. Omit or null = not a poll. |

Each poll object:

| Field | Type | Description |
|-------|------|--------------|
| `question` | string | The poll question (max 140 characters). |
| `options` | string[] | 2-4 answer options (max 30 characters each). |
| `duration` | string | `ONE_DAY` \| `THREE_DAYS` \| `SEVEN_DAYS` \| `FOURTEEN_DAYS`. |

A poll is mutually exclusive with media and a link share on that channel's post; if the request also carries `media_urls`/`media_ids` or a link share, the poll silently takes priority at publish time (same precedence LinkedIn's own media-vs-article rule uses), so don't send both.

Post a **different** poll to the profile and the page in one call by setting both keys:

```json
{
  "linkedin_poll": {
    "linkedin": { "question": "What should WE build?", "options": ["A", "B"], "duration": "SEVEN_DAYS" },
    "linkedin_page": { "question": "What should our COMPANY build?", "options": ["A", "B", "C"], "duration": "FOURTEEN_DAYS" }
  }
}
```

`linkedin_poll` is also accepted on `PATCH /posts/{id}`; it replaces the whole object wholesale (both channels), so send the full desired state, not just the channel you're changing. `GET /posts/{id}` echoes it back. Set a channel's key to `null` (or send `linkedin_poll: null`) to clear that channel's poll (or every poll) and revert to a normal post.

## Media requirements

| Media | Requirement |
|-------|-------------|
| Image | JPEG or PNG, max 10 MB |
| Video | MP4 or MOV, up to 10 minutes, max 5 GB |

LinkedIn supports text-only posts, a single image, a multi-image carousel, or video. Mixing image and video in the same post is not supported.

Posting 2 or more images publishes LinkedIn's native document carousel: OmniSocials renders the images into a single PDF and uploads it as a document, the same swipeable format LinkedIn's own composer produces, instead of a plain multi-image gallery. There's no image-count limit enforced on our side. When the images are the pages of a PDF you uploaded, LinkedIn receives that original file instead; see [Original PDF](#original-pdf).

Prefer the plain multi-image gallery for a specific post? Set `carousel_as_images: true` on the `linkedin` and/or `linkedin_page` option object (the same objects that carry `first_comment`). The flag is independent per channel and is ignored for text-only posts, single images, videos, and polls:

```json
{
  "content": { "default": "Five looks from the spring shoot" },
  "accounts": ["your-linkedin-account-id"],
  "media_urls": ["https://example.com/1.jpg", "https://example.com/2.jpg"],
  "linkedin": { "carousel_as_images": true }
}
```

On `PATCH /posts/{id}` the flag merges per channel: `true` sets it, `false` (or `null`) reverts that channel to the default document carousel, and omitting it keeps the current value. `GET /posts/{id}` echoes it back inside each channel's option object.

## Original PDF

[Uploading a PDF](/media#upload-a-pdf-as-a-carousel) renders its pages to images and keeps the original file, either as one item per page or as one document item (`pdf_mode: "document"`, the web library's default). When a LinkedIn post's images are exactly the rendered pages of one PDF, in order and unchanged, LinkedIn receives that original file as the document instead of one rebuilt from the page images. Text stays sharp, in-document links keep working, viewers download the real file, and every page is included, even past the 20-slide cap. Nothing to set: attach the slide `media_ids` the upload returned, in order.

The check is per channel and per post. Reordering, removing or editing a slide, or adding another image, means the images are no longer that PDF's pages, and the document is rebuilt from the slides as before. The same applies to a PDF over 100 MB or 300 pages (LinkedIn's document limits).

To send the rebuilt document on purpose, for example to publish a flattened copy, set `document_source: "slides"` on the channel's option object:

```json
{
  "content": { "default": "Our Q3 strategy deck" },
  "accounts": ["your-linkedin-account-id"],
  "media_ids": ["201", "202", "203"],
  "linkedin": { "document_source": "slides" }
}
```

On `PATCH /posts/{id}` it merges per channel: `"slides"` sets it, `"original_pdf"` (or `null`) reverts to the default, and omitting it keeps the current value. `GET /posts/{id}` echoes `document_source` inside the channel's option object when it is set to `"slides"`. The option is ignored when the images are not a PDF's pages, when `carousel_as_images` is `true`, and for text-only posts, single images, videos and polls.

## Alt text

Pass an accessibility description per media item by using an object entry instead of a bare string (works in `media_urls` and `media_ids`). It is sent as LinkedIn's `altText` on images: both single-image posts and multi-image galleries, where each image carries its own description. Video and documents don't support alt text (a LinkedIn limitation):

```json
{
  "content": { "default": "Team offsite highlights" },
  "accounts": ["your-linkedin-account-id"],
  "media_urls": [
    { "url": "https://example.com/team.jpg", "alt": "Six people around a whiteboard covered in sticky notes" }
  ]
}
```

The same `alt` also delivers to Mastodon, Bluesky, X, Pinterest, and Instagram (images), so one description covers a cross-platform post.

## Mentions

LinkedIn mentions must reference either another LinkedIn member or a company, using the URN format the LinkedIn API expects. OmniSocials exposes mention handling through the `mentions` field on the post body. See the API reference for the full shape.

## Limitations

- Stories and reels are not available through LinkedIn's public API
- LinkedIn enforces a 3,000 character limit per post. Posts exceeding this are rejected.
- Posting to company pages requires the connecting user to be an admin of the page
- The personal profile channel (`linkedin`) requires the Community Management API product on LinkedIn Developer, which is automatically configured for OmniSocials customers
