# LinkedIn: coming soon

Source: https://postgale.com/docs/linkedin

LinkedIn support in Postgale is coming soon: posts with images, video and documents, mentions, first comments, comments and reactions, and analytics for profiles and company pages through the Postgale REST API and MCP server.

> **Coming soon.** LinkedIn is not available yet: LinkedIn accounts cannot be connected in production, so none of the endpoints or
> tools below work against LinkedIn today. This page describes the planned integration and may change before launch.

Postgale will publish to LinkedIn personal profiles and company pages with LinkedIn's Posts API: text, up to 20 images, one video with a
custom thumbnail, or one document shown as a carousel, with mentions and a first comment. Comments, replies, deletes and reactions work
on company pages, and analytics cover posts, profiles and pages. Everything is available over the [REST API](https://postgale.com/docs/api) (paths relative
to `https://postgale.com/api/v1`) and the [MCP server](https://postgale.com/docs/mcp) (`create_post`, `list_comments`, `moderate_comment`, `like_post`,
`manage_published_post`, `linkedin_organizations`, `linkedin_resolve_mention`, `linkedin_analytics`). Each connected profile or page is
one account with platform `linkedin`; its `platformAccountId` is the author URN (`urn:li:person:…` or `urn:li:organization:…`).

## Connect an account

Ask for a connect link and send the member to it. They sign in to LinkedIn (OAuth 2.0), then pick what to connect: their own profile
and any company page they administer. Each pick becomes its own account. Tokens are stored encrypted; LinkedIn tokens last 60 days and
are refreshed automatically when LinkedIn issues a refresh token (365 days).

GET /api/v1/connect/linkedin:
```bash
curl https://postgale.com/api/v1/connect/linkedin?externalId=cust-1 \
  -H 'Authorization: Bearer sk_…'

# {"authUrl":"https://postgale.com/api/oauth/linkedin/consent?state=…","expiresInSeconds":600}
```

For links started through the API the pick is a pending connection, like Facebook Pages: `GET /connect/pending` lists it (or take its id
from the `connection.pending` webhook), `GET /connect/pending/{id}` shows the options and `POST /connect/pending/{id}/select` with
`{optionIds}` connects them. MCP: `select_connection`.

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/accounts/{id}/linkedin-organizations` | The company pages the account's member administers |
| PUT | `/accounts/{id}/linkedin-organization` | Switch the account between the personal profile and a page: `{accountType: "personal"}` or `{accountType: "organization", selectedOrganization: {id}}`. The account keeps its id and posts |
| GET | `/accounts/{id}/health` | Token validity, granted scopes and `profile.followersCount` |

## Publish

Text is limited to 3,000 characters. A post carries one kind of media: images, one video or one document, never mixed. Images, videos
and documents are uploaded to LinkedIn by Postgale; a video or document target stays `publishing` until LinkedIn has processed the file,
then the worker creates the post.

POST /api/v1/posts:
```bash
curl -X POST https://postgale.com/api/v1/posts \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"content":"Our 2027 industry report is out. Thanks @[https://www.linkedin.com/company/acme] for the data!","platforms":[{"accountId":"acc_linkedin","platformSpecificData":{"mediaItems":[{"type":"document","url":"https://media.postgale.com/…/report.pdf"}],"documentTitle":"2027 Industry Report","firstComment":"Read it online: https://example.com/report"}}],"publishNow":true}'
```

| platformSpecificData | What it does |
| --- | --- |
| `mediaItems` | `[{type: image\|video\|document, url, thumbnail?, title?, altText?}]`: up to 20 images (JPEG, PNG, GIF, ≤8 MB each), or one video (MP4, MOV, AVI; `thumbnail` sets a custom cover, best effort), or one document (PDF, PPT, PPTX, DOC, DOCX; ≤100 MB, ≤300 pages). `type` defaults from the URL extension |
| `mediaUrl` / `mediaUrls` / `mediaType` | Shorthand for media items |
| `documentTitle` | Title shown on a document post (default: the media item `title`, then the file name) |
| `firstComment` | Posted as the first comment right after publishing, as the post's author (≤1,250 characters; mentions become plain names). A failure is only a warning |
| `organizationUrn` | Post as another company page the member administers (`urn:li:organization:<id>`), with the same account |

**Mentions.** Write `@[Name](urn:li:person:…)` or `@[Name](urn:li:organization:…)` in the content and it is published as written, or put a
profile or company link inside `@[…]` (`@[https://www.linkedin.com/in/jane-doe]`, `@[Jane](https://www.linkedin.com/in/jane-doe)`) and
Postgale looks it up when publishing and uses LinkedIn's own name. Custom text must be the person's first, last or full name (or the
company's exact name), else LinkedIn's name is used and the target gets a warning. At most 10 link mentions per post; a link that cannot
be resolved fails the publish. Looking up a person needs the account to administer a LinkedIn page; company lookups do not.

GET /api/v1/accounts/{id}/linkedin-mentions:
```bash
curl https://postgale.com/api/v1/accounts/acc_linkedin/linkedin-mentions?url=linkedin.com/company/acme \
  -H 'Authorization: Bearer sk_…'

# {"urn":"urn:li:organization:42","type":"organization","displayName":"Acme","mentionFormat":"@[Acme](urn:li:organization:42)","vanityName":"acme"}
```

Hashtags (`#launch`) stay hashtags, and LinkedIn's reserved characters are escaped for you.

## Edit and delete

| Method | Path | What it does |
| --- | --- | --- |
| POST | `/posts/{id}/edit` | Replace the text of the live post (no time window; media cannot change) |
| POST | `/posts/{id}/unpublish` | Delete the post from LinkedIn |

## Comments and reactions

LinkedIn lets apps read comments on company pages only. Comment ids are LinkedIn's comment URNs, `urn:li:comment:(<post urn>,<id>)`.

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/inbox/comments?accountId=&platformPostId=` | Comments on a post (`urn:li:share:…`, `urn:li:ugcPost:…`, or an activity number) |
| POST | `/inbox/comments` | Comment on a post as the account |
| POST | `/inbox/comments/{commentId}/reply` | Reply to a comment |
| DELETE | `/inbox/comments/{postId}?commentId=` | Delete a comment (the composite URN, or the numeric id with the post in the path) |
| POST / DELETE | `/inbox/comments/{commentId}/like` | React to a comment, or remove the reaction: `reactionType` LIKE (default), PRAISE, EMPATHY, INTEREST, APPRECIATION or ENTERTAINMENT |
| POST / DELETE | `/inbox/posts/{postId}/like` | React to any post as the account (also another account's post), same `reactionType` |

New comments on posts published through Postgale in the last 14 days arrive as `comment.received` webhooks: the worker reads them every
`LINKEDIN_COMMENT_POLL_MINUTES` (default 15), since LinkedIn does not push comments to apps. Writes are queued for a person while
approval applies (202 `pending_approval`).

## Analytics

| Method | Path | What it does |
| --- | --- | --- |
| GET | `/analytics/posts/{id}` | Post metrics: impressions, reach, likes (reactions), comments, shares; saves and sends on profiles, clicks on pages |
| GET | `/accounts/{id}/linkedin-post-analytics?urn=` | The same for any post by URN |
| GET | `/accounts/{id}/linkedin-aggregate-analytics` | Profiles: totals (`aggregation=TOTAL`) or a daily series (`DAILY`, needs `fromDate`) across every post of the member |
| GET | `/analytics/linkedin/org-aggregate-analytics?accountId=` | Pages: impressions, clicks, engagement rate, follower gains, page views, `followers_gained` / `followers_lost`; `metricType` `total_value` or `time_series`; up to 89 days |
| GET | `/analytics/linkedin/demographics?accountId=` | Pages: followers by job function, seniority, industry and company size |
| GET | `/accounts/{id}/linkedin-post-reactions?urn=` | Pages: the individual reactions on a post |

Profile post metrics only cover posts the member authored. Metrics LinkedIn cannot serve are listed in `unavailableMetrics`, never
reported as zero.

## LinkedIn's rules to know

- **Duplicates**: LinkedIn rejects content it considers a duplicate of an earlier post, even weeks later.
- **No DMs**: LinkedIn's messaging API is closed to third-party apps.
- **Polls, reposts, articles and audience targeting** are not supported yet.
