All articles

LinkedIn Posting API: How to Post from Code (2026)

How the LinkedIn posting API works in 2026: Share on LinkedIn vs Community Management, scopes, review, the versioned Posts API, and media uploads.

9 min read

LinkedIn Posting API: How to Post from Code (2026)

LinkedIn has no single "posting API". It has an endpoint that creates posts, POST https://api.linkedin.com/rest/posts, and several products that decide whose posts you are allowed to create. Which one you need depends on one question: are you posting as a person or as a company page?

This guide covers both routes, the scopes, the review, the versioning headers, and the three-step upload flow for images and video. Every LinkedIn detail links to LinkedIn's docs on Microsoft Learn. The last section shows the same post sent through Bulkit.

Built for developers

Ship social integrations in hours, not quarters

One REST API, predictable errors, and per-network quirks handled for you — so social posting stops being a quarter-long project.

Which LinkedIn API do you use to post?

There are two products to know:

Share on LinkedIn Community Management API
Posts as The signed-in member The member, and company pages they admin
Key scope w_member_social w_organization_social (plus w_member_social)
Access Self-serve, added from the Products tab Application and review by LinkedIn
Who can apply Any developer Registered legal organizations, commercial use only

Share on LinkedIn is the self-serve product: add it under Products in the Developer Portal and your app gets w_member_social. That page still shows the older /v2/ugcPosts endpoint. The Posts API "replaces the ugcPosts API", and w_member_social is the permission it lists for posting on behalf of a member. New code should use /rest/posts.

To post as a page you need the Community Management API, which grants w_organization_social. That scope only works for pages where the signed-in member is an ADMINISTRATOR, DIRECT_SPONSORED_CONTENT_POSTER or CONTENT_ADMIN.

What permissions does the LinkedIn posting API need?

From the Posts API permissions table:

Scope What it allows
w_member_social Post, comment and like on behalf of the authenticated member
r_member_social Read the member's posts. Restricted, approved users only
w_organization_social Post, comment and like on behalf of an organization (page role required)
r_organization_social Read an organization's posts, comments and likes (page role required)

Two things catch people out. w_member_social is write-only, so you can create a member's post but cannot list their posts back without the restricted r_member_social. And per LinkedIn's OAuth docs, requesting a different scope set than the one a member already granted invalidates all of their previous access tokens.

How do you get access to the Community Management API?

It is a two-stage review, described on LinkedIn's Community Management App Review page.

Development tier. You fill in an access request form. LinkedIn checks the use case, a verified business email (personal addresses "won't pass the vetting process"), your organization and its website, and that a super admin of your company's LinkedIn Page has verified the app. The API is "only available to registered legal organizations for commercial use cases only."

Development tier is a sandbox with limits, per Increasing Access:

  • 500 API calls per app per 24 hours
  • 100 API calls per member per 24 hours
  • No BATCH_GET calls
  • Webhook push notifications disabled
  • You are expected to finish your integration within 12 months

Standard tier has no restrictions. To get it you submit a second form, test credentials, and a screen recording that walks through each use case: the full OAuth flow, a post to the page, how comments are displayed, and which commenter profile fields your app shows. If you are rejected at either stage, you can't reapply with the same app. You create a new one and start over.

LinkedIn gives no timeline for either review. Plan for days to weeks.

How does LinkedIn API versioning work?

Every call to /rest/ needs two headers, per the versioning docs:

Linkedin-Version: 202610
X-Restli-Protocol-Version: 2.0.0
  • Versions are named YYYYMM and ship monthly. The latest as of this writing is 202610 (October 2026).
  • Each version is supported for at least one year, then sunset.
  • There is no default. A request without Linkedin-Version gets an error, and so does one with a sunset version.
  • Right now every Marketing API page carries a notice that version 202511 sunsets on November 16, 2026.

In practice, keep the version in one constant and set a reminder to bump it every few months. A version hardcoded once and forgotten stops working when it reaches its sunset date.

How do you create a text post?

The minimal organic post from the Posts API docs:

curl -X POST 'https://api.linkedin.com/rest/posts' \
  -H 'Authorization: Bearer {INSERT_TOKEN}' \
  -H 'X-Restli-Protocol-Version: 2.0.0' \
  -H 'Linkedin-Version: 202610' \
  -H 'Content-Type: application/json' \
  --data '{
    "author": "urn:li:organization:5515715",
    "commentary": "Sample text Post",
    "visibility": "PUBLIC",
    "distribution": {
      "feedDistribution": "MAIN_FEED",
      "targetEntities": [],
      "thirdPartyDistributionChannels": []
    },
    "lifecycleState": "PUBLISHED",
    "isReshareDisabledByAuthor": false
  }'
  • author is a person URN (urn:li:person:{id}) or an organization URN (urn:li:organization:{id}).
  • Success is a 201. The post ID comes back in the x-restli-id response header, as urn:li:share:… or urn:li:ugcPost:….
  • author, visibility, distribution and lifecycleState are all required. Leaving one out returns MISSING_FIELD.
  • commentary uses LinkedIn's "little" text format. Mentions look like @[Devtestco](urn:li:organization:2414183), and the text has to match the entity's name exactly (case-sensitive) or it renders as plain text.

Organic posts support text, images, video, documents, articles, multi-image and polls. Carousels are sponsored-only.

How do you post an image or video on LinkedIn?

You never attach a file to the post request. You upload it first, get a URN back, and put the URN in the post.

Images (Images API):

  1. POST https://api.linkedin.com/rest/images?action=initializeUpload with {"initializeUploadRequest": {"owner": "urn:li:organization:…"}}.
  2. The response has an uploadUrl, its uploadUrlExpiresAt, and the image URN (urn:li:image:…).
  3. Upload the bytes to uploadUrl.
  4. Create the post with "content": {"media": {"id": "urn:li:image:…", "altText": "…"}}.

Images must be JPG, GIF or PNG, under 36,152,320 pixels, and GIFs can have up to 250 frames.

Video (Videos API) is a multipart upload:

  1. POST /rest/videos?action=initializeUpload with owner, fileSizeBytes, and uploadCaptions / uploadThumbnail flags.
  2. The response has uploadInstructions: one entry per part, each with firstByte, lastByte and its own uploadUrl. Parts are 4 MB (bytes 0–4194303, then 4194304–8388607, and so on).
  3. PUT each part to its URL and keep the ETag header from every response.
  4. POST /rest/videos?action=finalizeUpload with the video URN, the uploadToken, and uploadedPartIds: the ETags, in the same order as the instructions.
  5. Wait for the video's status to reach AVAILABLE (it goes through WAITING_UPLOAD and PROCESSING; PROCESSING_FAILED comes with a processingFailureReason).
  6. Create the post with "content": {"media": {"title": "…", "id": "urn:li:video:…"}}.

LinkedIn's spec for feed video is MP4, three seconds to 30 minutes. Upload URLs typically expire 30 days after initialization.

Link posts don't scrape the page. LinkedIn says the Posts API "does not support URL scraping", so you set content.article.source, title, description and a thumbnail image URN yourself. Without them, the card is a bare URL.

What breaks in production?

  • Tokens last 60 days. LinkedIn's OAuth docs say "all access tokens are issued with a 60-day lifespan." Programmatic refresh tokens are "available for a limited set of partners." Everyone else sends the member back through the OAuth flow. That flow skips the consent screen if they are still logged in and the old token hasn't expired yet. Build the "please reconnect" path before you launch.
  • Rate limits are daily. Share on LinkedIn lists 150 requests per member and 100,000 per application per day (UTC). Community Management's Development tier is far tighter (see above).
  • Version sunsets. See above: one header, one reminder.
  • Media isn't ready yet. Posting a video URN before it is AVAILABLE fails. Poll the video, or retry the post with backoff.
  • Retries can double-post. A timeout on POST /rest/posts doesn't tell you whether the post was created. Check before you resend.

How do you post to LinkedIn with Bulkit?

Bulkit doesn't skip LinkedIn's rules. It runs them for you. You still create your own LinkedIn app, and there are two, matching the two products above:

  • Personal profiles (linkedin): an app with Share on LinkedIn and Sign In with LinkedIn using OpenID Connect. Both are approved instantly, and any member can connect. See the LinkedIn app guide.
  • Company pages (linkedin_page): a separate, new app with the Community Management API. Bulkit's LinkedIn Page app guide notes that LinkedIn only grants it to an app with no other products, and that nothing connects until LinkedIn approves the request.

The 60-day login still applies. When it runs out, the account shows Needs reconnect in Bulkit.

After that, uploads, URNs, version headers and link cards are handled for you. From the LinkedIn platform page: 3,000 characters, up to 20 images, or one video (3 seconds to 30 minutes), or one PDF published as a swipeable document. Extra blocks (up to 2) are posted as comments under the post. Options go in channels[].options:

Key What it does
visibility PUBLIC (default) or CONNECTIONS. Profiles only
document_title Title shown under a PDF
poll_question, poll_options, poll_duration A poll (2 to 4 answers)
article_url, article_title, article_description, article_thumbnail A link card

A post can have one of files, a poll or a link card, not two. Here is the link card from the docs, sent with the Node SDK:

import { Bulkit } from "bulkit";

const bulkit = new Bulkit(); // reads BULKIT_API_KEY

const post = await bulkit.posts.create({
  intent: "schedule",
  scheduledAt: "2026-11-01T09:00:00Z",
  channels: [
    {
      channelId: "CHANNEL_LINKEDIN",
      options: {
        article_url: "https://example.com/blog/build-time",
        article_title: "How we cut our build time in half",
      },
      blocks: [{ content: "New on the blog 👇" }],
    },
  ],
});

As with the native API, LinkedIn doesn't read the linked page for you. Without article_title the card shows only the address, and without article_thumbnail it has no image.

For video, upload the file first and pass its id in mediaIds. media.uploadMultipart() splits and uploads large files in parallel, so the init, parts, ETags and finalize steps above are done for you:

import { openAsBlob } from "node:fs";

const video = await bulkit.media.uploadMultipart(await openAsBlob("launch.mp4"), {
  fileName: "launch.mp4",
  mimeType: "video/mp4",
  concurrency: 4,
});

Company pages also return analytics (impressions, likes, comments, shares, clicks), and you can read and reply to comments as the page. LinkedIn doesn't let apps read comments on personal profiles, so that isn't available there. The full feature list is on the LinkedIn channel page.

Native or Bulkit?

Go native if LinkedIn is your only network, you post as one page, and you're happy to own token renewal and version bumps. The API is well documented, and the steps above are the whole job.

Use Bulkit if LinkedIn is one of several networks, or you post for many people and pages. It's the same request for every network, and it costs $4.99 a month with 500 actions included, then $0.01 per action. To try it from TypeScript, the Node SDK page has the setup. To let an AI agent draft and schedule LinkedIn posts, connect it over MCP.

Built for developers

Ship social integrations in hours, not quarters

One REST API, predictable errors, and per-network quirks handled for you — so social posting stops being a quarter-long project.

Keep reading