Back to Blog
linkedinapidevelopersautomation

How to Post to LinkedIn via API in 2026: The New Posts API, Versioning, and the Link-Preview Trap

M
Mel Owen
8 min read

Short answer: you post to LinkedIn today with POST /rest/posts, not the endpoint every old tutorial still shows you.

If you are here because you hit LinkedIn's platform-side posting caps rather than its developer API, our LinkedIn scheduling limits guide covers that separately. This post is about building against the API directly.

Longer answer: if you learned LinkedIn's API a few years ago, or you copied a Stack Overflow snippet from 2022, you are building against an endpoint LinkedIn has been quietly walking away from. The old /v2/ugcPosts call still works for some integrations, but it has been flagged legacy since 2023 and none of LinkedIn's new documentation leads with it anymore. The current path is the Posts API, and it comes with a couple of gotchas that will cost you an afternoon if nobody warns you first: a version header LinkedIn silently requires on every call, and a link-preview behavior that quietly disappeared.

What changed: ugcPosts vs the new Posts API

/v2/ugcPosts was LinkedIn's general-purpose "create a piece of user-generated content" endpoint. It worked for text, images, video and articles, but the request shape was awkward (nested specificContent blocks keyed by content type) and LinkedIn stopped extending it.

POST /rest/posts is the replacement. It is flatter, it is the endpoint LinkedIn's current docs point you to for text, image, video, article, document and poll posts, and it is where new features land first. If you are starting a fresh integration in 2026, build against /rest/posts. If you inherited an integration built on ugcPosts, budget time to migrate it, since legacy endpoints on LinkedIn tend to lose support attention well before they get a hard shutoff date.

Getting access (Share on LinkedIn product, review tiers)

Before you can call anything, you need a LinkedIn developer app with the Share on LinkedIn product added. This is the product that grants the w_member_social scope for posting on a member's behalf.

Basic access to Share on LinkedIn is reportedly free and does not require the heavier partner-program review that gates some of LinkedIn's other APIs [hedge: access tiers and review requirements have shifted before and are worth re-checking against LinkedIn's current developer portal before you build]. What you do need, regardless of tier, is a verified company page tied to your developer app and a working OAuth redirect URL. Set both up before you touch code, since the app review step will bounce you back to fix them anyway.

Auth: scopes and tokens

For posting as an individual member, request the w_member_social scope during the OAuth authorization step. Once a member has authorized your app, you exchange the authorization code for an access token the normal OAuth 2.0 way:

POST https://www.linkedin.com/oauth/v2/accessToken
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={authorization_code}
&client_id={your_client_id}
&client_secret={your_client_secret}
&redirect_uri={your_redirect_uri}

The resulting access token is tied to that member's urn:li:person:{id}, which you get from the member's profile lookup and which you will need in every post you publish for them.

For posting to an organization page, the scope is w_organization_social instead, and the authorizing user must be an admin of that page. More on that below.

Your first post: POST /rest/posts walkthrough

Here is a minimal text post from a member account:

curl -X POST https://api.linkedin.com/rest/posts \
  -H "Authorization: Bearer {access_token}" \
  -H "Linkedin-Version: 202509" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{
    "author": "urn:li:person:{person_id}",
    "commentary": "Shipped a new feature today. Here is what changed and why.",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

A successful call returns a 201 Created with an x-restli-id response header holding the new post's URN. Save that URN if you plan to fetch analytics or delete the post later.

See how TimeToPost can help you implement these strategies.

Posting as an organization

Posting on behalf of a company page is nearly the same call, with two differences. The author field points at the organization's URN instead of a person's, and the authorizing token needs w_organization_social plus admin rights on that page:

curl -X POST https://api.linkedin.com/rest/posts \
  -H "Authorization: Bearer {access_token}" \
  -H "Linkedin-Version: 202509" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{
    "author": "urn:li:organization:{org_id}",
    "commentary": "We just published our Q3 roadmap update.",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

If the token belongs to someone who is not a designated admin on that organization page, LinkedIn returns an authorization error, not a helpful one. Confirm page-admin status in the LinkedIn UI before you spend an hour debugging your headers.

The version header trap

This is the one that catches almost everyone the first time. Every call to /rest/posts, and to most of LinkedIn's REST endpoints under the newer versioning scheme, requires a Linkedin-Version header in YYYYMM format, for example 202509 for September 2026. Miss it, and the call fails or silently falls back to different default behavior depending on the endpoint, which is worse than a clean error because it can look like it half-worked.

Two practical rules: pin the version header to a specific month rather than always chasing the newest one, so a LinkedIn-side change does not break your integration without warning, and re-test against a newer version deliberately on your own schedule rather than LinkedIn's. Treat it the same way you would treat a Stripe API version pin.

If you are building a multi-platform poster and comparing how much version and header discipline each platform demands, our X (Twitter) API guide covers the same tradeoffs on that side.

Why your link previews disappeared

If you migrated from ugcPosts and your posts used to show a rich link card automatically, and now they show up as bare text with a raw URL, you have hit the most common Posts API surprise. The new API does not auto-scrape a shared URL and generate a preview card for you the way the old flow sometimes appeared to. You have to build the article card yourself, explicitly, in the request body:

curl -X POST https://api.linkedin.com/rest/posts \
  -H "Authorization: Bearer {access_token}" \
  -H "Linkedin-Version: 202509" \
  -H "X-Restli-Protocol-Version: 2.0.0" \
  -H "Content-Type: application/json" \
  -d '{
    "author": "urn:li:person:{person_id}",
    "commentary": "New post on the blog: what changed in the LinkedIn API this year.",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "content": {
      "article": {
        "source": "https://example.com/blog/post-slug",
        "title": "What Changed in the LinkedIn API This Year",
        "description": "A walkthrough of the Posts API migration."
      }
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'

Add the content.article block and the card renders. Leave it out and post a bare URL in commentary, and you get plain text with a link in it. This is not a bug you can work around with formatting. It is the API asking you to be explicit about what used to happen implicitly.

Rate limits and token lifetimes

Access tokens are valid for roughly 60 days, and LinkedIn's refresh tokens are valid for roughly 365 days, so plan a refresh job well inside that 60-day window rather than waiting for a failed call to tell you the token died. On the posting side, LinkedIn is reported to apply a per-member limit around 100 API-driven posts per day [hedge: LinkedIn does not publish an exhaustive public rate-limit table for this endpoint, so treat this as a practical ceiling to design around rather than a documented guarantee]. If you are running a scheduler across many connected accounts, throttle per-account rather than assuming a shared budget across your whole app.

Scheduling on top of the API

LinkedIn's Posts API has no native scheduledAt field. There is no way to hand LinkedIn a post and a future timestamp and have their servers hold it. Scheduling is something you build on your own side: store the draft, hold it in a queue, and fire the POST /rest/posts call yourself at the right moment with a scheduler process, cron job, or a platform that handles it for you. TimeToPost publishes natively to X today; a LinkedIn connector exists in the product but is not yet open to customers, so for now the version-header pinning, org-vs-personal token handling, and refresh-job plumbing described above are still yours to build if you want LinkedIn live. If you want to time those posts against when your own audience actually engages rather than a generic best-practice slot, our best time to post on LinkedIn breakdown is the place to start, and the character counter will keep your commentary field inside LinkedIn's 3,000-character cap before you ever hit send.

FAQ

Do I need LinkedIn's Marketing Developer Platform to just post updates? No. Posting through Share on LinkedIn only requires the Share on LinkedIn product and w_member_social or w_organization_social, not the full partner marketing program.

Can I still use ugcPosts if my integration already works? It has been flagged legacy since 2023 and still functions in some integrations today, but LinkedIn's current documentation and new features are built around /rest/posts, so migrating sooner is safer than waiting for a deprecation notice.

What happens if I forget the Linkedin-Version header? Calls typically fail outright or behave inconsistently depending on the endpoint. Always send it, and pin it to a specific month you control.

Why does my link show as plain text instead of a card? Because the Posts API does not auto-generate link previews. You have to include an explicit content.article block with a source, title and description in the request body.

Can one access token post to both my personal profile and my company page? No. Personal posts need a token scoped with w_member_social and the person's URN as author. Organization posts need w_organization_social, admin rights on the page, and the organization's URN as author. They are separate authorization contexts even if the same human approved both.

Is there a sandbox to test posts without going live? LinkedIn does not offer a dedicated sandbox for the Posts API. Most developers test against a personal or team-owned page set to a restricted visibility level before pointing the integration at production accounts.

Related posts

Put these strategies into action

TimeToPost helps you schedule content, track performance, and grow your audience, all in one place.