Skip to main content
Socialift
Documentation sections
SDK reference

LinkedIn client: OAuth, profile and posts

LinkedIn is the LinkedIn API client in the Socialift Python SDK. It connects a member through 3-legged OAuth and OpenID Connect (openid, profile, email), exchanges, introspects and — where allowed — refreshes tokens, reads the profile from userinfo, and creates, lists and deletes text or link posts on the member's behalf via the Posts API. LinkedIn private messages and Lead Gen Forms are not part of this client.

from socialift import LinkedIn10 methodsas of socialift 1.3.0Updated:

How do I set up the LinkedIn client?

Create the client with LinkedIn(client_id=..., client_secret=...). LinkedIn.from_env() reads LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET and LINKEDIN_API_VERSION. The Client ID and Client Secret are on your app's Auth tab in the LinkedIn Developer Portal, where you also add the redirect URL — see the "LinkedIn app setup" guide.

Available scopes depend on the products added to the app: Sign In with LinkedIn using OpenID Connect gives openid, profile, email; Share on LinkedIn gives w_member_social. The SDK requests these four by default. Reading a member's posts needs r_member_social, a restricted permission LinkedIn grants to approved users only.

The Posts API (/rest/*) requires a LinkedIn-Version: YYYYMM header on every call; the SDK takes it from api_version. LinkedIn supports each monthly version for at least one year and answers sunset versions with HTTP 426, so keep api_version or LINKEDIN_API_VERSION on an active version.

python
import asyncio
import os

from socialift import LinkedIn

async def main():
    # 1) Explicit arguments
    li = LinkedIn(
        client_id=os.environ["LINKEDIN_CLIENT_ID"],
        client_secret=os.environ["LINKEDIN_CLIENT_SECRET"],
        api_version="202609",  # an active LinkedIn-Version (YYYYMM)
    )
    # 2) Or from the environment: LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET,
    #    LINKEDIN_API_VERSION
    # li = LinkedIn.from_env()

    async with li:
        me = await li.me(os.environ["LINKEDIN_TOKEN"])  # from exchange_code()
        print(me.id, me.full_name, li.person_urn(me.id))

asyncio.run(main())

Client and sign-in

Creating the client, the authorization URL and exchanging code for a token.

exchange_codefrom_envlogin_url

Exchanges the callback code for an access token at /oauth/v2/accessToken (form-urlencoded) and returns a TokenBundle.

await li.exchange_code(code: str, *, redirect_uri: str) -> socialift.models.TokenBundle
  • LinkedIn rule: the code is valid for 30 minutes, and redirect_uri must match the authorization request.
  • Access tokens last 60 days; refresh_token is only returned if programmatic refresh is enabled for your app.
  • Raises ApiError if the response has no access_token.

from_env

classmethod

Creates a client from the LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET and LINKEDIN_API_VERSION environment variables so keys stay out of code.

LinkedIn.from_env(**kwargs) -> LinkedIn
  • Empty values are not validated by the constructor: a missing client_id surfaces as ConfigurationError in login_url.
  • Without LINKEDIN_API_VERSION the SDK default version is used — check that it is still active at LinkedIn.

Builds the LinkedIn authorization URL (/oauth/v2/authorization) to send the member to; it sends no request. Scopes are space-separated, defaulting to openid, profile, email, w_member_social.

li.login_url(
    *,
    redirect_uri: str,
    state: Optional[str] = None,
    scopes: Optional[list[str]] = None,
) -> str
  • Raises ConfigurationError without client_id.
  • state is CSRF protection: reject the callback if it does not match what you sent (LinkedIn recommendation). The SDK does not store it.
  • LinkedIn rule: members cannot pick individual scopes, and requesting a scope different from the one previously granted invalidates all previous access tokens.
  • redirect_uri must match one of the redirect URLs on the app's Auth tab.
Example — python
url = li.login_url(redirect_uri="https://siz.uz/cb", state=state)

Token lifecycle

Refreshing with a refresh token and introspecting token status.

introspectrefresh

Checks a token at /oauth/v2/introspectToken and returns LinkedIn's response as a dict: active, status, expires_at, scope, auth_type and more. Use it to see whether a token still works and when it expires.

await li.introspect(token: str) -> dict
  • status is active, expired or revoked; scope is only returned for 3-legged OAuth tokens.
  • LinkedIn rule: with valid credentials but a token from another app, the response is 200 with active: false — nothing is raised.
Example — python
info = await li.introspect(token)

refresh

async

Gets a new access token with a refresh token (grant_type=refresh_token) and returns a TokenBundle, so members do not have to sign in again when a token expires.

await li.refresh(refresh_token: str) -> socialift.models.TokenBundle
  • LinkedIn issues programmatic refresh tokens only to approved Marketing Developer Platform partners; otherwise the member must authorize again.
  • Refresh tokens last 365 days and refreshing does not extend that; the new access token lasts 60 days.
  • Raises ApiError if the response has no access_token.
Example — python
bundle = await li.refresh(refresh_token)

Profile

The OpenID Connect profile and the author URN.

meperson_urn

me

async

Returns the member's profile from OpenID Connect userinfo (/v2/userinfo) as a Profile: id (= sub), name, picture and email. You need sub to build the post author URN.

await li.me(token: str) -> socialift.models.Profile
  • Needs the openid and profile scopes, plus email for the address; per LinkedIn, email is optional and may be missing.
  • Profile.username is the email, or sub when absent; Profile.raw includes an extra language derived from the locale.

Required permissions:openidprofileemail

Example — python
me = await li.me(token)

person_urn

staticmethod

Turns sub into urn:li:person:{sub} — the author value for posts and create_post. A static method that sends no request.

LinkedIn.person_urn(sub: str) -> str
  • create_post and posts wrap any value not starting with urn: this way automatically.

Required permissions:r_member_socialw_member_social

Example — python
posts = await li.posts(token, author=li.person_urn(sub), count=10)
Example — python
post = await li.create_post(token, author=li.person_urn(sub), text="Yangiliklar!",
                             article_url="https://siz.uz/blog")

Posts

Posts API: listing, creating and deleting a member's posts.

create_postdelete_postposts

Creates a text post (optionally with a link) on the member's behalf via the Posts API and returns {id, body}; id is the post URN from the x-restli-id header. visibility is PUBLIC or CONNECTIONS.

await li.create_post(
    token: str,
    *,
    author: str,
    text: str,
    visibility: str = 'PUBLIC',
    article_url: Optional[str] = None,
    article_title: Optional[str] = None,
) -> dict
  • Requires w_member_social (Share on LinkedIn product).
  • For a link the SDK sets content.article with source and title (defaulting to the URL). The Posts API does not scrape URLs, so no thumbnail or description is generated.
  • Any other visibility raises ConfigurationError.
  • No image, video or document upload in this method.

Required permissions:w_member_social

Example — python
post = await li.create_post(token, author=li.person_urn(sub), text="Yangiliklar!",
                             article_url="https://siz.uz/blog")

Deletes a post by URN (urn:li:share:... or urn:li:ugcPost:...) and returns {deleted: True, urn}. The URN is fully encoded in the request path.

await li.delete_post(token: str, post_urn: str) -> dict
  • LinkedIn rule: deletes are idempotent — an already deleted post also returns 204; batch delete is not supported.
  • Requires w_member_social; this cannot be undone.

Required permissions:w_member_social

Example — python
await li.delete_post(token, post_urn)

posts

async

Lists an author's posts with the Posts API q=author finder and returns LinkedIn's response (elements, paging). Paginate with start and count.

await li.posts(token: str, *, author: str, start: int = 0, count: int = 10) -> dict
  • Member posts require r_member_social, which LinkedIn restricts to approved users; on 403 the SDK raises a PermissionError with an explanation.
  • LinkedIn rule: count defaults to 10 with a maximum of 100; results are sorted by last-modified time, newest first.
  • A page can hold fewer than count items even when more posts exist — use paging.links to find the next page.

Required permissions:r_member_social

Example — python
posts = await li.posts(token, author=li.person_urn(sub), count=10)

Methods shared by all clients

Every HTTP client (Telegram, Facebook, Instagram, LinkedIn, TikTok, Ads) has these low-level methods — for calling an endpoint the SDK doesn't wrap yet or for reading pages of results. The connection is closed automatically when the async with block ends.

request

async

Sends an arbitrary HTTP request and returns the JSON response — for endpoints the SDK doesn't wrap with a dedicated method yet. You pass the token and signing parameters yourself in params or headers (access_token, and for Meta appsecret_proof via socialift.crypto.appsecret_proof).

await li.request(
    method: str,
    url: str,
    *,
    params: Optional[Mapping[str, Any]] = None,
    data: Optional[Any] = None,
    json: Optional[Any] = None,
    headers: Optional[Mapping[str, str]] = None,
) -> Any
  • None values in params are not sent.
  • Rate limits (RateLimitError) and transient server errors (HTTP 429, 500, 502, 503, 504) are retried according to RetryPolicy; a Retry-After header is honoured. Network failures are raised as TransportError.

get

async

Shorthand for request("GET", ...).

await li.get(url: str, **kw) -> Any

post

async

Shorthand for request("POST", ...).

await li.post(url: str, **kw) -> Any

delete

async

Shorthand for request("DELETE", ...).

await li.delete(url: str, **kw) -> Any

Follows Meta's cursor pagination (paging.next) and returns every data item as a single list.

await li.paginate(
    url: str,
    *,
    params: Optional[Mapping[str, Any]] = None,
    max_items: int = 500,
    page_size: int = 100,
) -> list[dict]
  • max_items (default 500) is a safety cap — a large account can have tens of thousands of records. page_size sets the limit of each request.

close

async

Closes the HTTP session the SDK created itself (a session you passed in is left open). Called automatically when you use async with Client(...).

await li.close() -> None

Frequently asked questions

How long does a LinkedIn access token last?

Per LinkedIn, all access tokens are currently issued with a 60-day lifespan (expires_in). Programmatic refresh tokens are available only to approved Marketing Developer Platform partners and last 365 days; using one does not extend the refresh token's own lifetime. Without a refresh token the member has to authorize again.

Why do I get 403 when reading LinkedIn posts?

posts() reads an author's posts with q=author, and member posts require r_member_social. LinkedIn marks that permission as restricted and grants it to approved users only. Without it the SDK raises a PermissionError with an explanatory message.

What does HTTP 426 mean in the LinkedIn API?

The LinkedIn-Version header on a /rest/* call points to a version that is no longer active. LinkedIn supports each version for at least one year and then returns 426 with the NONEXISTENT_VERSION code. Update api_version or LINKEDIN_API_VERSION in the SDK to an active YYYYMM value.

How do I set the author of a LinkedIn post?

A personal post's author is urn:li:person:{sub}, where sub is the Profile.id returned by me(). person_urn(sub) builds that string, and create_post and posts wrap any value that does not start with urn: for you.

Does Socialift support LinkedIn messages or Lead Gen Forms?

No. The LinkedIn client covers sign-in, tokens, the profile and posts only. The SDK has no methods for private messages or Lead Gen Forms.

Sources

  1. LinkedIn: Authorization Code Flow (3-legged OAuth)
  2. LinkedIn: Programmatic refresh tokens
  3. LinkedIn: Token introspection
  4. LinkedIn: Sign In with LinkedIn using OpenID Connect
  5. LinkedIn: Posts API
  6. LinkedIn: API versioning

Related pages