Skip to main content
Socialift
Guide

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

LinkedIn products and permissions for posting — Capability matrix by platform
Product or programPermissionsAccessWhat it enables
Sign In with LinkedIn using OpenID Connectopenid, profile, emailOpen, self-serve in the Products tabMember identity: sub, name, picture, email
Share on LinkedInw_member_socialOpen, self-serve in the Products tabPost, comment and like on behalf of the member
Community Management APIw_organization_social, r_organization_social and moreVetted: Development tier first, then Standard tierCompany page posts, comments, analytics
Reading member postsr_member_socialClosed — LinkedIn is not accepting requestsA member's posts, comments and likes
Messages APInoRestricted to approved partnersMember-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

  1. 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.

  2. 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 #.

  3. Send the member to authorization

    Redirect to https://www.linkedin.com/oauth/v2/authorization with response_type=code, client_id, redirect_uri, state and a space-delimited scope such as openid profile email w_member_social.

  4. Exchange the code within 30 minutes

    Check state, then POST grant_type=authorization_code, code, client_id, client_secret and redirect_uri as form data to https://www.linkedin.com/oauth/v2/accessToken. The code has a 30-minute lifespan.

  5. Get the person URN

    Call GET https://api.linkedin.com/v2/userinfo with the token. The sub field is the member identifier; the post author is urn:li:person:{sub}.

  6. Create the post

    POST to https://api.linkedin.com/rest/posts with LinkedIn-Version: YYYYMM and X-Restli-Protocol-Version: 2.0.0. A 201 response carries the post ID in the x-restli-id header.

Sign in and publish with Python

Set api_version explicitly to a supported YYYYMM release — LinkedIn does not apply the latest version by default.

linkedin_post.py
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

Posts API vs legacy ugcPosts — Capability matrix by platform
AspectPosts APIugcPosts (legacy)
EndpointPOST /rest/postsPOST /v2/ugcPosts
StatusCurrent; LinkedIn states it replaces ugcPostsStill shown in the Share on LinkedIn guide
Version headerLinkedIn-Version: YYYYMM requiredNot versioned
Protocol headerX-Restli-Protocol-Version: 2.0.0X-Restli-Protocol-Version: 2.0.0
Post textcommentaryspecificContent → com.linkedin.ugc.ShareContent → shareCommentary
MediaURNs from the Images, Videos and Documents APIs; MultiImage and Poll postsAssets registered via /v2/assets?action=registerUpload
Created post IDx-restli-id header, 201X-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

LinkedIn tokens: lifetime and refresh — Capability matrix by platform
ItemLinkedIn behaviorSocialift SDK
Authorization code30-minute lifespan; use it immediatelyexchange_code
Access token60 days (expires_in = 5184000); about 500 characters, plan storage for 1000+Result of exchange_code
Programmatic refresh tokenApproved partners only (Marketing Developer Platform); valid 365 days, and the TTL does not reset on refreshrefresh
No refresh tokenRun authorization again; the consent screen is skipped if the member is logged in to LinkedIn and the token has not expiredlogin_url
Scope changeRequesting a different scope invalidates previously issued access tokensno
Token statusPOST /oauth/v2/introspectToken returns active, status, expires_at, scopeintrospect

yesno

Check a token, re-authorize, delete a post

linkedin_tokens.py
# 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.

← Back to blog

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.

Sources

  1. LinkedIn — Posts API
  2. LinkedIn — Sign In with LinkedIn using OpenID Connect
  3. LinkedIn — 3-legged OAuth flow
  4. LinkedIn — Programmatic refresh tokens
  5. LinkedIn — API versioning
  6. LinkedIn — Community Management overview

Related pages