Posting to LinkedIn through the API
To post on LinkedIn through the API as a member, add two self-serve products to your app — Sign In with LinkedIn using OpenID Connect and Share on LinkedIn — request openid profile email w_member_social, read the member's sub from /v2/userinfo, and POST to /rest/posts with author urn:li:person:{sub} and a LinkedIn-Version header. Company page posts need the vetted Community Management API.
Updated:
What do you need to post on LinkedIn via API?
A LinkedIn developer app with two open products, a member access token with the w_member_social scope, and the member's person URN. Share on LinkedIn grants w_member_social without special approval; Sign In with LinkedIn using OpenID Connect provides openid, profile and email so you can identify the member. Posting as a company page is a separate, vetted program.
LinkedIn describes Open Permissions as the only permissions available to all developers without special approval. For posting that means profile, email and w_member_social. Everything else — from organization posts to reading a member's activity — requires LinkedIn's approval.
The open identity scopes are openid, profile and email from OpenID Connect, and member details come from the /v2/userinfo endpoint.
LinkedIn products and permissions for posting
| Product or program | Permissions | Access | What it enables |
|---|---|---|---|
| Sign In with LinkedIn using OpenID Connect | openid, profile, email | Open, self-serve in the Products tab | Member identity: sub, name, picture, email |
| Share on LinkedIn | w_member_social | Open, self-serve in the Products tab | Post, comment and like on behalf of the member |
| Community Management API | w_organization_social, r_organization_social and more | Vetted: Development tier first, then Standard tier | Company page posts, comments, analytics |
| Reading member posts | r_member_social | Closed — LinkedIn is not accepting requests | A member's posts, comments and likes |
| Messages API | no | Restricted to approved partners | Member-to-member messages |
yesnoScroll the table sideways
Lead Sync (LinkedIn lead gen forms) is a separate program with its own application. Socialift does not provide LinkedIn messaging or lead forms.
How to publish your first LinkedIn post via API
Create an app and add products
In the LinkedIn Developer Portal open My Apps, create an app, then add Sign In with LinkedIn using OpenID Connect and Share on LinkedIn on the Products tab.
Configure the redirect URL
On the Auth tab copy the Client ID and Client Secret and add an absolute HTTPS redirect URL. LinkedIn ignores query parameters in it and rejects URLs containing
#.Send the member to authorization
Redirect to
https://www.linkedin.com/oauth/v2/authorizationwithresponse_type=code,client_id,redirect_uri,stateand a space-delimitedscopesuch asopenid profile email w_member_social.Exchange the code within 30 minutes
Check
state, then POSTgrant_type=authorization_code,code,client_id,client_secretandredirect_urias form data tohttps://www.linkedin.com/oauth/v2/accessToken. The code has a 30-minute lifespan.Get the person URN
Call
GET https://api.linkedin.com/v2/userinfowith the token. Thesubfield is the member identifier; the post author isurn:li:person:{sub}.Create the post
POST to
https://api.linkedin.com/rest/postswithLinkedIn-Version: YYYYMMandX-Restli-Protocol-Version: 2.0.0. A201response carries the post ID in thex-restli-idheader.
Sign in and publish with Python
Set api_version explicitly to a supported YYYYMM release — LinkedIn does not apply the latest version by default.
import os
from socialift import LinkedIn
REDIRECT_URI = "https://app.example.com/oauth/linkedin/callback"
def client() -> LinkedIn:
return LinkedIn(
client_id=os.environ["LINKEDIN_CLIENT_ID"],
client_secret=os.environ["LINKEDIN_CLIENT_SECRET"],
api_version=os.environ.get("LINKEDIN_API_VERSION", "202609"),
)
async def authorize_url(state: str) -> str:
async with client() as li: # default scopes: openid profile email w_member_social
return li.login_url(redirect_uri=REDIRECT_URI, state=state)
async def publish(code: str) -> str:
async with client() as li:
bundle = await li.exchange_code(code, redirect_uri=REDIRECT_URI)
me = await li.me(bundle.access_token)
post = await li.create_post(
bundle.access_token,
author=li.person_urn(me.id),
text="We just published our API changelog for September.",
visibility="PUBLIC",
)
return post["id"] # urn:li:share:… or urn:li:ugcPost:…create_post accepts visibility of PUBLIC or CONNECTIONS and reads the new post URN from the x-restli-id response header, because the body does not contain it.
article_url= and article_title= attach an article link. The Posts API does not scrape URLs, so the post shows only what you send: this method sets the link and title, without a thumbnail.
Posts API vs legacy ugcPosts
| Aspect | Posts API | ugcPosts (legacy) |
|---|---|---|
| Endpoint | POST /rest/posts | POST /v2/ugcPosts |
| Status | Current; LinkedIn states it replaces ugcPosts | Still shown in the Share on LinkedIn guide |
| Version header | LinkedIn-Version: YYYYMM required | Not versioned |
| Protocol header | X-Restli-Protocol-Version: 2.0.0 | X-Restli-Protocol-Version: 2.0.0 |
| Post text | commentary | specificContent → com.linkedin.ugc.ShareContent → shareCommentary |
| Media | URNs from the Images, Videos and Documents APIs; MultiImage and Poll posts | Assets registered via /v2/assets?action=registerUpload |
| Created post ID | x-restli-id header, 201 | X-RestLi-Id header, 201 |
Choose /rest/posts for new integrations: LinkedIn publishes a dedicated migration guide from ugcPosts to the Posts API.
How the LinkedIn-Version header works
Versioned LinkedIn APIs live under https://api.linkedin.com/rest/, and every request must include LinkedIn-Version in YYYYMM format. LinkedIn ships versions monthly and supports each for at least one year. The latest version when this article was written is 202609 (September 2026).
LinkedIn does not fall back to the newest version: a missing header or a sunset version returns an error response. The documentation currently warns that version 202510 will be sunset on October 15, 2026; older versions are no longer listed as supported.
Endpoints under /v2/, such as /v2/userinfo, are not versioned. In the Socialift SDK the version is set once per client with api_version= (or LINKEDIN_API_VERSION when you use LinkedIn.from_env()) and sent with every /rest/ call, together with X-Restli-Protocol-Version: 2.0.0. Review it at least once a year.
LinkedIn tokens: lifetime and refresh
| Item | LinkedIn behavior | Socialift SDK |
|---|---|---|
| Authorization code | 30-minute lifespan; use it immediately | exchange_code |
| Access token | 60 days (expires_in = 5184000); about 500 characters, plan storage for 1000+ | Result of exchange_code |
| Programmatic refresh token | Approved partners only (Marketing Developer Platform); valid 365 days, and the TTL does not reset on refresh | refresh |
| No refresh token | Run authorization again; the consent screen is skipped if the member is logged in to LinkedIn and the token has not expired | login_url |
| Scope change | Requesting a different scope invalidates previously issued access tokens | no |
| Token status | POST /oauth/v2/introspectToken returns active, status, expires_at, scope | introspect |
yesno
Check a token, re-authorize, delete a post
# client() and REDIRECT_URI come from linkedin_post.py
async def ensure_active(token: str, state: str) -> str | None:
async with client() as li:
info = await li.introspect(token)
if info.get("active"):
return None
return li.login_url(redirect_uri=REDIRECT_URI, state=state) # send the member back
async def remove_post(token: str, post_urn: str) -> None:
async with client() as li:
await li.delete_post(token, post_urn)delete_post URL-encodes the URN in the request path, as LinkedIn requires. Deletion is idempotent: deleting an already deleted post returns 204 again.
Posting as a company page: Community Management API
To post as an organization, the author is urn:li:organization:{id} and the token needs w_organization_social. That permission is limited to members with the ADMINISTRATOR, DIRECT_SPONSORED_CONTENT_POSTER or CONTENT_ADMIN role on the page, and it is only issued through the Community Management API.
Community Management API access is vetted. LinkedIn currently offers it only to registered legal organizations for commercial use cases: you verify a business email, provide the organization's legal name, address, website and privacy policy, and a super admin of your LinkedIn Page verifies the app. Development tier comes first with limited call volume; Standard tier requires a screen recording of every use case. The Development tier request is made on a new app that has no other API products.
Once approved, the same create_post call works with an organization URN as author, and posts(token, author=...) lists the organization's posts with r_organization_social.
Member data obtained through these APIs falls under LinkedIn's Marketing API terms: it must not be used for sales, recruiting or CRM enrichment, and member social activity data may not be stored for more than 48 hours.
What the LinkedIn API does not give you
Reading a member's own posts requires r_member_social, which LinkedIn marks as a closed permission and is not accepting requests for. In practice li.posts() with a person URN returns 403 for most apps; the SDK turns that into a PermissionError with an explanation.
Messaging is restricted to approved partners, and even then a message must follow a specific member action — automated or scheduled sends are not allowed. Lead gen form data belongs to the separate Lead Sync program. Socialift does not offer LinkedIn messaging or lead forms.
Limits are daily: the Share on LinkedIn documentation lists 150 requests per member and 100,000 per application, counted in UTC days.
Frequently asked questions
Can I post to a LinkedIn company page with Share on LinkedIn?
No. Share on LinkedIn grants w_member_social for member posts only. A company page needs w_organization_social, issued through the Community Management API to members with an admin or content role on the page.
How long does a LinkedIn access token last?
All access tokens are currently issued with a 60-day lifespan. Programmatic refresh tokens go only to approved partners; everyone else re-runs authorization, and the consent screen is skipped if the member is logged in and the token is still valid.
Is the LinkedIn ugcPosts API deprecated?
LinkedIn states that the Posts API replaces the ugcPosts API and publishes a migration guide. New integrations should use /rest/posts with the LinkedIn-Version header.
Why does LinkedIn reject my request because of the version?
The /rest/ request is missing the LinkedIn-Version header, or the version has been sunset. Each version is supported for at least one year — update the header to a supported YYYYMM value.
Can I read my own LinkedIn posts or analytics via API?
Reading a member's posts needs r_member_social, which is closed. Member post statistics are part of the use cases covered by the vetted Community Management API.
Can I send LinkedIn messages through the API?
The Messages API is restricted to approved partners, and every message must result from a specific member action. Socialift does not work with LinkedIn messages.