API Guides

LinkedIn Posting API Guide 2026: Post to Profiles and Company Pages

Complete LinkedIn Posting API guide for 2026: OAuth 2.0 setup, scopes, Node.js and Python examples, rate limits, error handling, and a faster unified API path.
August 07, 2026 by Jesse Eisenbart16 min read
Watercolor illustration of code publishing to a personal profile post and a company page post

TLDR: The LinkedIn Posting API lets you publish content programmatically to personal profiles and company pages. You need OAuth 2.0, the w_member_social scope for profiles or w_organization_social for pages, and a verified developer app with the “Share on LinkedIn” product enabled. Access tokens last 60 days, posting quotas are enforced per member and per app, and media uploads are a multi-step flow. If LinkedIn is one of several platforms you need, a unified social media API like PostZen replaces all of that with a single endpoint.

The LinkedIn Posting API lets your code create posts — text, images, videos, documents — on a member’s profile or a company page without anyone opening LinkedIn. That single capability is what powers scheduling tools, AI content pipelines, and every “publish to LinkedIn” button you have ever seen inside a SaaS product.

This guide walks the whole path: developer app setup, OAuth 2.0, your first post in Node.js and Python, media uploads, rate limits, and the errors you will actually hit. It ends with an honest look at when you should skip the direct integration entirely.

If you want the shorter head-to-head comparison instead, read How to Post to LinkedIn via API: Direct LinkedIn API vs PostZen.

What is the LinkedIn Posting API?

The LinkedIn Posting API is part of LinkedIn’s Community Management API. It is a versioned REST API that creates, reads, updates, and deletes posts through https://api.linkedin.com/rest/posts. It replaced the older ugcPosts endpoint, which you should not use for new integrations.

The API supports:

  • Text posts to personal profiles and company pages
  • Single-image and multi-image posts
  • Video posts
  • Article shares with link previews
  • Document posts (PDFs and presentations)
  • Polls

It is the same API behind commercial social media management tools. Nothing about it is reserved for big vendors — but as you will see, getting approved access and keeping the integration healthy is where the real cost lives.

LinkedIn API vs manual posting

CapabilityManual postingLinkedIn Posting API
Time per postMinutes of copy-paste per accountOne request from your backend
Bulk schedulingNative scheduler only, one post at a timeQueue as many posts as your quota allows
Multi-account managementConstant account switchingAll connected accounts through one integration
Error handlingYou notice failures when you notice themStatus codes, retries, and alerting in your code
Approval workflowsScreenshots in SlackDraft, review, and publish states in your own product

Why automate LinkedIn posting

Watercolor illustration of a queue of scheduled posts publishing along a winding path

Consistency is the entire game on LinkedIn. Only a small fraction of members — commonly estimated at around 1% — post weekly, and that group captures the overwhelming majority of feed impressions. Showing up on a schedule is a structural advantage, and an API is how you make a schedule survive busy weeks.

Automation earns its keep in three places:

  • Scalable publishing. A content calendar executes itself instead of depending on someone remembering to press “post.”
  • Multi-account operations. Agencies and B2B products manage many profiles and company pages from one system instead of juggling logins.
  • Product features. If you are building a tool with social publishing in it, the API is not optional — it is the feature.

Format variety matters too. LinkedIn’s own guidance and most third-party studies agree that video and document posts outperform plain text for reach, so an integration that only handles text posts leaves the highest-performing formats on the table.

Prerequisites: what you need before writing code

Gather these before you start, because each one gates the next step:

  1. A LinkedIn company page. Required to create a developer app, even if you only plan to post to personal profiles. A placeholder page works.
  2. A LinkedIn developer app created at developer.linkedin.com.
  3. App verification. The company page admin has to verify the app.
  4. Products enabled on the app: “Share on LinkedIn” for posting and “Sign In with LinkedIn using OpenID Connect” for authentication.
  5. A privacy policy URL. LinkedIn checks for one during review.

OAuth 2.0 scopes you need

ScopePurposeHow you get it
openid, profile, emailAuthentication and basic profile dataSelf-serve with the OpenID Connect product
w_member_socialPost, comment, and react on behalf of a memberSelf-serve with the Share on LinkedIn product
w_organization_socialPost on behalf of a company pageCommunity Management API access review
r_organization_socialRead company page posts and commentsCommunity Management API access review

Request only the scopes you actually use. Over-asking makes the consent screen scarier for users and gives LinkedIn’s reviewers more to question.

Getting API access and surviving app review

Personal-profile posting is the easy tier: add the “Share on LinkedIn” product and w_member_social unlocks without a review cycle.

Company-page posting is the hard tier. The w_organization_social scope requires applying for Community Management API access, and LinkedIn scrutinizes the use case. Reviews can take weeks, and rejections often arrive with little actionable feedback. The applications that pass share three traits:

  • A specific, member-benefiting use case — “our customers draft posts in our product and publish to their own pages,” not “marketing automation.”
  • A complete app profile — logo, accurate description, working privacy policy URL, and a verified company page.
  • Scope requests that match the story. Asking for read scopes you never mention in the use case is a red flag.

Budget for the review to be on your critical path, not a formality. If your roadmap includes more platforms than LinkedIn, remember that each network runs its own version of this gauntlet — that repeated cost is the strongest argument for a unified social media API, where the platform approvals are already done.

OAuth 2.0 authentication, step by step

Watercolor illustration of an OAuth handshake between an app, a consent screen, and an access token

LinkedIn uses the three-legged OAuth 2.0 authorization code flow. Your app sends the user to LinkedIn, LinkedIn asks the user to approve your scopes, and the user comes back to your redirect URI with a one-time code that your server exchanges for an access token.

Step 1: Send the user to the authorization URL

// Node.js
const buildAuthUrl = (clientId, redirectUri, state) => {
	const params = new URLSearchParams({
		response_type: 'code',
		client_id: clientId,
		redirect_uri: redirectUri,
		scope: 'openid profile w_member_social',
		state, // random value you verify on the callback (CSRF protection)
	})
	return `https://www.linkedin.com/oauth/v2/authorization?${params}`
}

Step 2: Exchange the code for an access token

// Node.js
async function getAccessToken(code, clientId, clientSecret, redirectUri) {
	const response = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
		method: 'POST',
		headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
		body: new URLSearchParams({
			grant_type: 'authorization_code',
			code,
			client_id: clientId,
			client_secret: clientSecret,
			redirect_uri: redirectUri,
		}),
	})

	const data = await response.json()
	// data.expires_in is 5184000 seconds = 60 days
	return { accessToken: data.access_token, expiresIn: data.expires_in }
}

The same exchange in Python:

import requests

def exchange_code_for_token(code: str, client_id: str, client_secret: str, redirect_uri: str) -> dict:
    response = requests.post(
        'https://www.linkedin.com/oauth/v2/accessToken',
        data={
            'grant_type': 'authorization_code',
            'code': code,
            'client_id': client_id,
            'client_secret': client_secret,
            'redirect_uri': redirect_uri,
        },
        headers={'Content-Type': 'application/x-www-form-urlencoded'},
        timeout=30,
    )
    response.raise_for_status()
    data = response.json()
    return {
        'access_token': data['access_token'],
        'expires_in': data['expires_in'],  # 60 days
    }

Run the exchange server-side only. The client secret must never reach a browser or mobile app.

Token lifetimes are the trap

Access tokens expire after 60 days, and there is no silent renewal by default. Programmatic refresh tokens (valid for up to 365 days) exist, but LinkedIn only issues them to apps approved for specific partner programs. Everyone else has to send the user back through the consent flow before day 60.

A production integration therefore needs token expiry tracking, proactive “reconnect your account” prompts, and graceful handling of posts that fail because a token died early — for example when the user changed their password. This bookkeeping, multiplied across every platform you support, is most of the ongoing cost of direct integrations.

Publishing your first post

With a valid token, you post by sending JSON to https://api.linkedin.com/rest/posts. Two headers beyond authorization are mandatory and forgetting either is the most common first-request failure:

Authorization: Bearer YOUR_ACCESS_TOKEN
LinkedIn-Version: 202606
X-Restli-Protocol-Version: 2.0.0
Content-Type: application/json

LinkedIn-Version is a YYYYMM date. Versions eventually sunset, so this header is a maintenance obligation, not a constant.

The post payload

A minimal text post to a personal profile:

{
	"author": "urn:li:person:aBcDeFg123",
	"commentary": "Shipped our first post through the LinkedIn Posting API.",
	"visibility": "PUBLIC",
	"distribution": {
		"feedDistribution": "MAIN_FEED",
		"targetEntities": [],
		"thirdPartyDistributionChannels": []
	},
	"lifecycleState": "PUBLISHED",
	"isReshareDisabledByAuthor": false
}

The author field is a URN. Use urn:li:person:{id} for profiles (the id comes from the /v2/userinfo endpoint after OpenID sign-in) or urn:li:organization:{id} for company pages, where the authenticated user must hold an admin or content-poster role on the page.

Python example

import requests

def create_linkedin_post(access_token: str, author_urn: str, text: str) -> dict:
    headers = {
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json',
        'X-Restli-Protocol-Version': '2.0.0',
        'LinkedIn-Version': '202606',
    }

    payload = {
        'author': author_urn,
        'commentary': text,
        'visibility': 'PUBLIC',
        'distribution': {
            'feedDistribution': 'MAIN_FEED',
            'targetEntities': [],
            'thirdPartyDistributionChannels': [],
        },
        'lifecycleState': 'PUBLISHED',
        'isReshareDisabledByAuthor': False,
    }

    response = requests.post(
        'https://api.linkedin.com/rest/posts',
        headers=headers,
        json=payload,
        timeout=30,
    )

    if response.status_code == 201:
        return {'success': True, 'post_id': response.headers.get('x-restli-id')}
    return {'success': False, 'status': response.status_code, 'error': response.json()}

Note the success path: LinkedIn returns 201 Created with an empty body and puts the new post’s ID in the x-restli-id response header. Code that only parses response bodies will lose the post ID.

Node.js example

async function createLinkedInPost(accessToken, authorUrn, text) {
	const response = await fetch('https://api.linkedin.com/rest/posts', {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${accessToken}`,
			'Content-Type': 'application/json',
			'X-Restli-Protocol-Version': '2.0.0',
			'LinkedIn-Version': '202606',
		},
		body: JSON.stringify({
			author: authorUrn,
			commentary: text,
			visibility: 'PUBLIC',
			distribution: {
				feedDistribution: 'MAIN_FEED',
				targetEntities: [],
				thirdPartyDistributionChannels: [],
			},
			lifecycleState: 'PUBLISHED',
			isReshareDisabledByAuthor: false,
		}),
	})

	if (response.status === 201) {
		return { success: true, postId: response.headers.get('x-restli-id') }
	}
	return { success: false, status: response.status, error: await response.json() }
}

Posts are capped at 3,000 characters. Exceed it and the API rejects the request with a validation error, so enforce the limit in your product before the API does it for you.

Image and video uploads are a multi-step flow

Media posts do not travel in one request. The pattern for images:

  1. Initialize the upload via the Images API, passing the owner URN. LinkedIn returns an upload URL and an image URN (urn:li:image:{id}).
  2. Upload the binary to that URL with your access token.
  3. Reference the URN in your post payload’s content object.

Video follows the same register-upload-reference shape with extra steps: files upload in 4MB parts, you finalize the upload, and you wait for LinkedIn to finish processing before the video URN is usable. The Videos API accepts MP4 files from three seconds up to 30 minutes and 500MB.

The flow is reasonable API design. The engineering cost is everything around it: validating file types and sizes before upload, tracking half-finished uploads, mapping processing failures to messages a user can act on, and storing enough IDs to debug a failed post a week later.

LinkedIn API rate limits

LinkedIn enforces daily quotas per application and per member, resetting at midnight UTC. Exact numbers vary by API product and are not published as one universal table — your app’s real limits live in the Analytics tab of the developer portal. As a planning baseline, posting endpoints have commonly enforced quotas on the order of ~100–150 post creations per member per day, with much higher per-application ceilings.

Two rules keep you out of trouble:

  • Treat quotas as per-member budgets. One power user cannot be allowed to burn your whole app quota.
  • Never retry a 429 blindly. Back off and reschedule.
import time
import requests

def post_with_backoff(url: str, headers: dict, payload: dict, max_retries: int = 3):
    for attempt in range(max_retries):
        response = requests.post(url, headers=headers, json=payload, timeout=30)

        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            time.sleep(retry_after * (attempt + 1))
            continue

        return response

    raise RuntimeError('Rate limit retries exhausted')

In a real product this logic belongs in a queue worker, not an inline request handler — which means building a queue, a worker, and failure states. That scheduling infrastructure is a project of its own, and it is exactly what a social media scheduling API gives you out of the box.

Common errors and how to fix them

StatusWhat it usually meansFix
401 UnauthorizedToken expired, revoked, or missingSend the user back through OAuth; track expiry proactively
403 ForbiddenMissing scope, missing product, or no page roleCheck granted scopes and the member’s role on the organization
404 Not FoundBad URN or deleted resourceVerify the author URN format and that the entity exists
422 Unprocessable EntitySchema or validation failureCheck payload structure, character limits, and media URN states
429 Too Many RequestsDaily quota exhaustedBack off, queue, and respect Retry-After

The mistakes that produce most of those errors:

  • Omitting the LinkedIn-Version header, or pinning a version that has been sunset.
  • Omitting X-Restli-Protocol-Version: 2.0.0.
  • Building on ugcPosts. It is deprecated; use /rest/posts.
  • Ignoring token expiry until posts silently stop publishing on day 61.
  • Reading only response bodies and losing the post ID from the x-restli-id header.

Measuring what you publish

Publishing is half the loop; the other half is knowing what worked. LinkedIn exposes post analytics — impressions, unique reach, reactions, comments — through its Community Management APIs, and member-level post analytics have been available to approved third-party tools since 2025. Pulling those numbers into your own dashboard turns a posting tool into a feedback loop: which topics, formats, and time slots actually earn reach for each account.

Access to analytics scopes goes through the same review process as posting scopes, so decide up front whether your use case needs them and apply for everything in one review.

The faster path: one API for every platform

Everything above is for one platform. If your product also needs Instagram, TikTok, Facebook, YouTube, Threads, X, or Bluesky, you repeat the developer-app dance, the OAuth quirks, the media pipelines, and the error taxonomies for each one — and then maintain them all as the platforms change underneath you.

A unified social media API collapses that into one integration. With PostZen, connecting a LinkedIn account and publishing looks like this:

curl -X POST "https://api.postzen.dev/v1/posts" \
  -H "Authorization: Bearer $POSTZEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Launch announcement",
    "content": "We shipped the new release. Here is what changed and who it helps.",
    "publishNow": true,
    "platforms": [
      {
        "platform": "linkedin",
        "accountId": "account-id",
        "settings": { "visibility": "PUBLIC" }
      }
    ]
  }'

PostZen handles the OAuth connect flow, token refresh, media uploads via presigned URLs, and per-platform status tracking (scheduled, pending, publishing, published, failed). Swap publishNow for scheduledFor and the same request becomes a scheduled post — no queue infrastructure on your side. The same payload shape publishes to every other supported platform by adding entries to the platforms array.

Direct integration still makes sense when LinkedIn is your entire product surface and you need every platform-specific knob. For everyone shipping social publishing as a feature, the build-vs-buy math favors the unified API: check the API reference and pricing to see if it fits.

Frequently asked questions

What is the LinkedIn Posting API?

It is the part of LinkedIn’s Community Management API that creates and manages posts programmatically via https://api.linkedin.com/rest/posts. It supports text, images, video, documents, and polls on both personal profiles and company pages.

How do I authenticate with the LinkedIn API?

Through the OAuth 2.0 authorization code flow: register a developer app, request the right scopes (w_member_social for profiles, w_organization_social for pages), send the user to LinkedIn’s consent screen, and exchange the returned code server-side for an access token.

Can I schedule LinkedIn posts through the API?

Not natively — LinkedIn’s API publishes immediately. Scheduling means storing posts yourself and publishing them with a worker at the right time, or using an API like PostZen where scheduledFor on the post request does it for you.

How long do LinkedIn access tokens last?

Access tokens last 60 days. Refresh tokens valid up to 365 days exist but are limited to approved partner programs; most apps re-run the consent flow instead.

What are the rate limits for posting?

LinkedIn enforces daily per-member and per-application quotas that reset at midnight UTC; posting endpoints are commonly limited to roughly 100–150 post creations per member per day. Your app’s exact numbers are shown in the developer portal’s Analytics tab.

Can I post images and videos?

Yes, via a multi-step flow: initialize the upload to get an upload URL and media URN, upload the binary, then reference the URN in the post payload. Video adds processing time before the URN becomes usable.

How do I post to a company page?

Set author to the organization URN (urn:li:organization:{id}) and authenticate a user who holds an admin or content-poster role on that page, using the w_organization_social scope.

What is the difference between the Posts API and UGC Posts?

The Posts API (/rest/posts) is the current, versioned replacement for the deprecated ugcPosts endpoint. New integrations should only use the Posts API.

What is the maximum post length?

3,000 characters for both profiles and company pages. Longer content is rejected with a validation error.

Do I need LinkedIn app approval to use PostZen instead?

No. Your application integrates with PostZen’s API, and your users authorize their LinkedIn accounts through a hosted connect flow. The LinkedIn platform approvals, token refresh, and media handling live behind PostZen’s API.

Sources