# LinkedIn

Publish text, article links, images, video and PDF documents to LinkedIn profiles and Company Pages.


## Overview

The `linkedin` platform publishes with LinkedIn's versioned REST Posts API, to a personal profile or (with extra LinkedIn access) to Company Pages you administer. You can post:

- Text posts
- Link (article) posts with your own title and description
- 1–20 images
- 1 video
- A PDF document (LinkedIn is the only platform that accepts `"type": "document"` media)
- Company Page @mentions

## Connect the account

In **[Integrations](https://social-publish-kappa.vercel.app/dashboard/integrations)**, choose **LinkedIn** and approve access.

### App setup (self-hosted)

1. In the LinkedIn Developer Portal, open your app's **Auth** tab and add the redirect URL `https://social-publish-kappa.vercel.app/api/integrations/linkedin/callback` (override with `LINKEDIN_REDIRECT_URI`).
2. On the **Products** tab, add **Sign In with LinkedIn using OpenID Connect** and **Share on LinkedIn**. Both are self-serve.
3. Set `LINKEDIN_CLIENT_ID` and `LINKEDIN_CLIENT_SECRET`.

Scopes requested by default: `openid`, `profile`, `w_member_social`.

| Flag | Adds | Enables |
| --- | --- | --- |
| `LINKEDIN_ORG_SCOPES=true` | `w_organization_social`, `r_organization_social`, `rw_organization_admin` | Posting to Company Pages you administer, and Page post analytics |
| `LINKEDIN_MEMBER_ANALYTICS_SCOPES=true` | `r_member_postAnalytics` | Impressions, clicks and saves on personal posts |
| `LINKEDIN_API_VERSION=YYYYMM` | — | Overrides the LinkedIn API version sent with each request |

> [!WARNING]
> Only turn these flags on once LinkedIn has granted the matching product to the app behind `LINKEDIN_CLIENT_ID`. Requesting a scope the app doesn't have makes the whole consent screen fail with `unauthorized_scope_error`. Company Page posting needs the **Community Management API** product, which LinkedIn grants only after review and only to an app without other products, so it is usually a separate app. Members must reconnect after a flag is turned on.

LinkedIn access tokens last 60 days. Unless LinkedIn has issued your app refresh tokens, an expired token means the account must be reconnected.

## Post types & media rules

| Rule | Limit |
| --- | --- |
| Text | 3,000 characters |
| Images | Up to 20; JPG, PNG or GIF; up to 50 MB each |
| Video | 1, MP4 only, up to 500 MB |
| Images and video | Can't be combined |
| Document | 1 PDF, up to 100 MB (LinkedIn also limits it to 300 pages); no other media and no link |
| Document title | Required: `media[].title` or `options.documentTitle` |
| Link post | `articleUrl` is only used when no media is attached |
| Visibility | `CONNECTIONS` is only available for personal profiles |
| Mentions | Up to 20 Company Page mentions; each `text` must appear exactly in the post content |

LinkedIn does not fetch the linked page for article posts, so set `articleTitle` (it defaults to the site name) and `articleDescription` yourself.

## Options

| Option | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `visibility` | `PUBLIC` \\| `CONNECTIONS` | No | `"PUBLIC"` | Visibility. Connections only is available for personal profiles, not Company Pages. |
| `disableReshare` | boolean | No | `false` | Disable resharing |
| `articleUrl` | string (URL) | No |  | Link URL. Creates a link (article) post. Ignored when images or a video are attached. |
| `articleTitle` | string | No |  | Link title. Only used with a Link URL. LinkedIn does not fetch the page, so set the title yourself (defaults to the site name). |
| `articleDescription` | string | No |  | Link description. Only used with a Link URL. |
| `documentTitle` | string | No |  | Document title. Title shown on the PDF card (required when posting a PDF) |
| `mentions` | object[] | No |  | Mentions. Tag a company page. LinkedIn only supports mentioning company pages, not personal profiles. "Text to match" must appear exactly in your post content — it becomes the clickable @mention. Items: `name`, `organization`, `text` (max 20). |
For `mentions`, `organization` accepts an organization URN (`urn:li:organization:123`), a numeric ID or a vanity name. The first occurrence of `text` in the post becomes the clickable mention; to mention the same text twice, repeat it in the post and add two mentions.

## Examples

### PDF document post

```bash tab="cURL"
curl -X POST https://social-publish-api.onrender.com/v1/posts \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "content": "Our 2026 hiring playbook, free to download.",
    "media": [{
      "url": "https://cdn.example.com/hiring-playbook.pdf",
      "type": "document",
      "title": "Hiring Playbook 2026"
    }],
    "targets": [{
      "accountId": "3c8d1e2f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
      "options": { "visibility": "PUBLIC" }
    }],
    "publishNow": true
  }'
```

```ts tab="Node.js"
const res = await fetch("https://social-publish-api.onrender.com/v1/posts", {
  method: "POST",
  headers: {
    "x-access-key": process.env.RELAYCAST_API_KEY!,
    "content-type": "application/json",
  },
  body: JSON.stringify({
    content: "Our 2026 hiring playbook, free to download.",
    media: [
      { url: "https://cdn.example.com/hiring-playbook.pdf", type: "document", title: "Hiring Playbook 2026" },
    ],
    targets: [{ accountId: "3c8d1e2f-5a6b-4c7d-8e9f-0a1b2c3d4e5f", options: { visibility: "PUBLIC" } }],
    publishNow: true,
  }),
});
const { data } = await res.json();
```

```python tab="Python"
import os, requests

res = requests.post(
    "https://social-publish-api.onrender.com/v1/posts",
    headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
    json={
        "content": "Our 2026 hiring playbook, free to download.",
        "media": [{
            "url": "https://cdn.example.com/hiring-playbook.pdf",
            "type": "document",
            "title": "Hiring Playbook 2026",
        }],
        "targets": [{
            "accountId": "3c8d1e2f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
            "options": {"visibility": "PUBLIC"},
        }],
        "publishNow": True,
    },
)
data = res.json()["data"]
```

> [!TIP]
> A document can only go to LinkedIn accounts. If the same post also targets other platforms, they fail validation; send the PDF in a separate post.

### Article link with a Company Page mention

```json title="Request body"
{
  "content": "Thanks to Microsoft for hosting our meetup. Recap on the blog.",
  "targets": [{
    "accountId": "3c8d1e2f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
    "options": {
      "articleUrl": "https://example.com/blog/meetup-recap",
      "articleTitle": "Meetup recap: October 2026",
      "articleDescription": "Talks, slides and photos from the evening.",
      "mentions": [{ "name": "Microsoft", "organization": "microsoft", "text": "Microsoft" }],
      "disableReshare": false
    }
  }],
  "scheduledFor": "2026-11-04T08:00:00Z"
}
```

## Analytics & comments

- **Personal posts:** likes and comments. Impressions, reshares, link clicks and saves need `r_member_postAnalytics` (`LINKEDIN_MEMBER_ANALYTICS_SCOPES=true`).
- **Company Page posts:** impressions, clicks, likes, comments and shares through the Community Management API.

Comments and messaging are not supported for LinkedIn.

## Limitations & common errors

| Error | Cause |
| --- | --- |
| `LinkedIn posts allow 3000 characters` | Text is too long. |
| `LinkedIn posts can have images or one video, not both` | Mixed images and video. |
| `LinkedIn document posts can't include images or video` | A PDF was combined with other media. |
| `LinkedIn document posts can't include a link` | `articleUrl` set on a document post. |
| `Add a document title` | No `media[].title` or `documentTitle` for a PDF. |
| `"…" must appear exactly in the post content` | A mention's `text` isn't in the post. |
| Target fails: connections-only visibility | `visibility: "CONNECTIONS"` on a Company Page account. |
| Target fails: only MP4 / JPG, PNG and GIF | Unsupported media file type. |
| Target fails: duplicate post (HTTP 422) | LinkedIn rejected identical content. |
| Consent screen shows `unauthorized_scope_error` | A LinkedIn flag is on without the product approved for the app. |
| Account becomes `reconnect_required` | The 60-day token expired, or a 403 points at a missing product/permission. |
