How to post to LinkedIn from an API (company pages and personal profiles)
October 29, 2026
Posting to LinkedIn from code comes down to one request. What takes the time is everything around it: the app, the permissions, the tokens, and the difference between posting as a person and posting as a company page.
This post covers the request itself, the two permissions that decide what you can post as, and the shorter route through yeetpost. Everything about LinkedIn's API is as of October 2026, from LinkedIn's Posts API documentation.
#The request
LinkedIn's Posts API creates posts with a single POST:
curl -X POST 'https://api.linkedin.com/rest/posts' \
-H 'Authorization: Bearer {ACCESS_TOKEN}' \
-H 'X-Restli-Protocol-Version: 2.0.0' \
-H 'Linkedin-Version: {YYYYMM}' \
-H 'Content-Type: application/json' \
--data '{
"author": "urn:li:organization:5515715",
"commentary": "Sample text Post",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"lifecycleState": "PUBLISHED",
"isReshareDisabledByAuthor": false
}'Three things in there are easy to get wrong.
Both version headers are required. LinkedIn's docs say every API requires Linkedin-Version in YYYYMM format and X-Restli-Protocol-Version: 2.0.0. Leave one out and you get an error that does not always say which.
The post id is in a header, not the body. A successful create returns 201, and the id of the new post comes back in the x-restli-id response header, for example urn:li:share:6844785523593134080. If your code reads the JSON body for an id, it will find nothing.
Versions get retired. The documentation carries a deprecation notice that Marketing version 202511 sunsets on November 16, 2026. The Posts API also replaces the older ugcPosts API. If you copied an example from an old blog post, check which one it uses.
#Personal profile or company page
The difference is the author field and the permission your token carries.
| Posting as | author | Permission |
|---|---|---|
| A person | urn:li:person:{id} | w_member_social |
| A company page | urn:li:organization:{id} | w_organization_social |
w_member_social lets you post, comment and like on behalf of the authenticated member. w_organization_social does the same for an organization, but it is restricted to organizations where the authenticated member holds one of three company page roles: ADMINISTRATOR, DIRECT_SPONSORED_CONTENT_POSTER or CONTENT_ADMIN.
So the person who authorizes your app has to be an admin of the page, or hold one of the other two roles. A token from someone with no role on the page will not post to it. A 403 ACCESS_DENIED is LinkedIn's answer when the scope is missing or the member lacks the role.
Reading is a separate permission. r_member_social, for reading a member's posts, is restricted to approved users only. If your plan involves reading your own post history back, find out early whether you qualify. Our post on how to get LinkedIn API access goes through the tiers and the review.
#Images, links and what the API will not do
A text post is the easy case. Anything with media is a second step:
- An image post needs the image uploaded first through LinkedIn's Images API, which gives you a
urn:li:image:{id}to put in the post. Video and documents work the same way with their own APIs. - Several images in one organic post use the MultiImage API.
- An article post does not scrape the link for you. LinkedIn's docs say the Posts API does not support URL scraping, so you set the title, description and thumbnail yourself, and the thumbnail is an uploaded image.
- Deleting is
DELETE /rest/posts/{encoded urn}and answers204. Deletion is idempotent.
Mentions and hashtags go in the commentary field in a special format. A mention is @[Name](urn:li:organization:2414183) and the text has to match the organization's name exactly, case sensitive, or it renders as plain text.
#Where the time goes
The request is short. The work is around it:
- Create a LinkedIn app and get the products and permissions you need approved.
- Build the OAuth flow so a user can authorize you, and store their tokens.
- Refresh or re-authorize before tokens expire.
- Track which
urn:li:organizationids a given user can post to. - Track the API version header and migrate before a version sunsets.
If you are posting only from your own account, you can do this once and forget it. If you are building something for other people, each of those steps becomes a feature you maintain.
#The shorter route with yeetpost
yeetpost does the OAuth connection once, in the dashboard, and then you post with an API key. You authenticate with LinkedIn through LinkedIn's own consent screen, optionally add the company pages you manage, and each profile or page becomes one connected account with a slug. Your code never holds a LinkedIn token.
curl -X POST https://api.yeetpost.com/api/v2/post/linkedin \
-H "x-api-key: $YEETPOST_API_KEY" \
-d 'shipped the new api docs today'The body is the post. To post to a company page, connect the page and use its slug instead of linkedin. GET /connections lists your slugs.
For anything beyond plain text, use POST /posts. With an image and a first comment, which is where LinkedIn posts usually put the link:
curl -X POST https://api.yeetpost.com/api/v2/posts \
-H "x-api-key: $YEETPOST_API_KEY" \
-H "content-type: application/json" \
-d '{
"text": "we rewrote the scheduler. writeup in the comments.",
"connectionSlugs": ["linkedin"],
"mediaUrls": ["https://example.com/scheduler.png"],
"firstComment": "https://example.com/blog/rewriting-the-scheduler"
}'What LinkedIn takes through yeetpost, from the LinkedIn docs:
- Text up to 3,000 characters
- Up to 5 images per post, 8 MB each (png, jpeg, gif, webp), with alt text up to 1,000 characters
- A first comment up to 1,250 characters
- Delete:
DELETE /posts/{id}takes the post down - No threads. Asking for one on a LinkedIn connection comes back as
shape_unsupportedfor that item - Metrics read back: likes and comments for all posts, impressions and clicks for company page posts only
Posting to LinkedIn is free. Billing is per active connected account, and the first 2 are free. See pricing.
A refused first comment does not fail the post. The share is already live by the time the comment goes out, so the result is still sent with hasFirstComment: false. Read that field instead of assuming.
Scheduling and a dry run are one field each. scheduledFor takes an ISO 8601 time in the future, and validateOnly: true runs every check without sending.
#Which one to pick
Build directly on LinkedIn's API if LinkedIn is your product and you need things the API passes through only in part, like reading and analytics at scale. Use a wrapper if posting is a feature and you would rather not run an OAuth integration for it.
The LinkedIn posting API page has the limits and a connection walkthrough, and the docs have the full reference.