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.
Before you start: App verification
Before your app can publish to LinkedIn in production, it needs to pass LinkedIn’s verification process:
- App Creation — Create an app in the LinkedIn Developer Portal and configure it with your redirect URIs and permissions
- 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 organizationr_organization_social— Retrieve organization postsw_member_social— Create and publish posts on behalf of a memberr_member_social— Retrieve member posts
All API requests require these headers:
Authorization: Bearer {TOKEN}Linkedin-Version: {YYYYMM}X-Restli-Protocol-Version: 2.0.0Content-Type: application/jsonPublishing 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 dataPROCESSING— Being processed by LinkedInAVAILABLE— Ready to use in a postPROCESSING_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:
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.