Publishing to LinkedIn via API: A technical guide

Understanding permissions, the two posting APIs, image and video uploads, and API endpoints for LinkedIn content publishing.

Publishing to LinkedIn via API: A technical guide

Before you start: App verification

Before your app can publish to LinkedIn in production, it needs to pass LinkedIn’s verification process:

  1. App Creation — Create an app in the LinkedIn Developer Portal and configure it with your redirect URIs and permissions
  2. Advertising API Access — Request access to the Advertising API product through the portal. This is the only product needed for publishing

The verification process takes 1–2 weeks. LinkedIn may request additional information or documentation during review.

The two posting APIs

LinkedIn has two posting APIs. The legacy one — ugcPosts — is deprecated but still haunts search results like a highly-upvoted Stack Overflow answer from 2018. The current one is the Posts API (/rest/posts), and that’s what you will most likely use but it requires the beforementioned app verification. Everything in this guide is based on it.

Required permissions

To publish content on behalf of a LinkedIn organization, your app needs these permissions approved:

  • w_organization_social — Create and publish posts on behalf of an organization
  • r_organization_social — Retrieve organization posts
  • w_member_social — Create and publish posts on behalf of a member
  • r_member_social — Retrieve member posts

All API requests require these headers:

Authorization: Bearer {TOKEN}
Linkedin-Version: {YYYYMM}
X-Restli-Protocol-Version: 2.0.0
Content-Type: application/json

Publishing text posts

POST to /rest/posts:

{
"author": "urn:li:organization:5515715",
"commentary": "Your post text",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"lifecycleState": "PUBLISHED",
"isReshareDisabledByAuthor": false
}

Returns 201 Created with the post URN in the x-restli-id response header.

Publishing images

Images require uploading the asset first, then creating the post that references it.

Step 1: Upload the image

POST to /rest/images?action=initializeUpload:

{
"initializeUploadRequest": {
"owner": "urn:li:organization:5515715"
}
}

Returns an uploadUrl and an image URN (urn:li:image:{id}). Upload the image binary directly to the uploadUrl.

Supported formats: JPG, PNG, GIF (up to 250 frames). Maximum 36,152,320 pixels.

Step 2: Create the post

Once the image status is AVAILABLE, POST to /rest/posts with the image URN in the content:

{
"author": "urn:li:organization:5515715",
"commentary": "Your caption",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"lifecycleState": "PUBLISHED",
"content": {
"media": {
"id": "urn:li:image:C4E10AQFoyyAjHPMQuQ"
}
}
}

Publishing videos

Videos use a four-step upload flow. The video must reach AVAILABLE status before it can be referenced in a post.

Step 1: Initialize the upload

POST to /rest/videos?action=initializeUpload:

{
"initializeUploadRequest": {
"owner": "urn:li:organization:5515715",
"fileSizeBytes": 1055736,
"uploadThumbnail": true
}
}

Returns a video URN (urn:li:video:{id}), an uploadToken, and uploadInstructions — an array of parts with byte ranges and upload URLs.

Step 2: Upload video parts

Split the video into 4 MB chunks and upload each part to its assigned URL. Save the ETag from each response header — these are required to finalize the upload.

Video requirements: MP4 only, 3 seconds to 30 minutes, 75 KB to 500 MB.

Step 3: Upload thumbnail and captions (optional)

Upload a custom thumbnail (JPG or PNG) to the thumbnailUploadUrl from step 1. SRT caption files can be uploaded to the captionsUploadUrl.

Step 4: Finalize the upload

POST to /rest/videos?action=finalizeUpload:

{
"finalizeUploadRequest": {
"video": "urn:li:video:C5505AQH-oV1qvnFtKA",
"uploadToken": "<TOKEN_FROM_STEP_1>",
"uploadedPartIds": ["etag1", "etag2", "etag3"]
}
}

Once the video status is AVAILABLE, create the post referencing the video URN the same way as images — using content.media.id.

Publishing multi-image posts

Multi-image posts support 2–20 images in a single post. Upload each image individually via the Images API first, then create the post:

POST to /rest/posts:

{
"author": "urn:li:organization:5515715",
"commentary": "Multi-image post caption",
"visibility": "PUBLIC",
"distribution": {
"feedDistribution": "MAIN_FEED",
"targetEntities": [],
"thirdPartyDistributionChannels": []
},
"lifecycleState": "PUBLISHED",
"content": {
"multiImage": {
"images": [
{ "id": "urn:li:image:C4D22AQFttWMAaIqHaa", "altText": "First image" },
{ "id": "urn:li:image:C4D22AQG7uz0yPJh588", "altText": "Second image" }
]
}
}
}

altText is optional but recommended for accessibility. Maximum 4,086 characters per image.

Checking asset status

Image and video uploads are processed asynchronously. Poll the asset endpoint before referencing it in a post:

  • Images: GET /rest/images/{image-urn}
  • Videos: GET /rest/videos/{video-urn}

Status values:

  • WAITING_UPLOAD — Awaiting upload data
  • PROCESSING — Being processed by LinkedIn
  • AVAILABLE — Ready to use in a post
  • PROCESSING_FAILED — Upload or processing failed

Share statistics

Retrieve organic post performance for an organization page via GET /rest/organizationalEntityShareStatistics:

?q=organizationalEntity
&organizationalEntity=urn:li:organization:2414183
&timeIntervals=(timeRange:(start:1551398400000,end:1552003200000),timeGranularityType:DAY)

Available metrics: impressions, unique impressions, clicks, likes, comments, shares, and engagement rate.

Data covers a rolling 12-month window. Omit timeIntervals for lifetime totals, or set timeGranularityType to DAY or MONTH for time-series data. This endpoint covers organic posts only — sponsored content metrics require the Ad Analytics API.

The same video, through Postproxy

Here’s how it’s done with Postproxy. One simple request with only what matters:

Terminal window
curl -X POST "https://api.postproxy.dev/api/posts" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"post": {
"body": "We just shipped our biggest update this year. Here is what changed and why."
},
"profiles": ["linkedin"],
"media": ["https://example.com/video.mp4"]
}'

One request. Postproxy handles the chunked upload, ETag tracking, finalization, status polling, and post creation.

What Postproxy handles

Postproxy maintains approved permissions and handles the complexity:

  • All required permissions approved and OAuth 2.0 token management
  • Image uploads via the Images API
  • Video uploads including chunked upload, ETag tracking, and finalization
  • The upload-then-post flow for images, videos, and multi-image posts
  • Asset status polling before post creation
  • Rate limit monitoring and backoff handling

Your system sends content. Postproxy handles the LinkedIn-specific implementation.

Connect your LinkedIn page and start publishing through the Postproxy API.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.