Blog

LinkedIn document carousels: how the PDF swipe post actually works

· 3 min read · CyberFreezeDev

LinkedIn document carousels: how the PDF swipe post actually works

Every few months someone asks how to post a LinkedIn carousel through the API. The answer is that there is no such thing, and the thing they actually want has a different name.

The swipe post is a PDF

What people call a LinkedIn carousel is a document post. You upload a PDF, LinkedIn renders each page as a slide, and the feed shows it as a swipeable card. That is the entire mechanism.

So the API you want is the Documents API, not anything with "carousel" in the name.

Three calls

  1. Initialise the upload. POST /rest/documents?action=initializeUpload with the author URN. LinkedIn returns an uploadUrl and a document URN.
  2. PUT the bytes to that uploadUrl. This is a plain upload to a LinkedIn-owned URL, not an API call in the usual sense.
  3. Create the post. POST /rest/posts with the document URN in the content.media field.

Between two and three, the document has to finish processing. Poll GET /rest/documents/{urn} until its status is AVAILABLE.

The versioned endpoints have their own rules

The /rest/* endpoints are not the old /v2/* ones, and mixing them up produces errors that look like authentication problems.

Every versioned call needs two headers:

LinkedIn-Version: 202506
X-Restli-Protocol-Version: 2.0.0

The version is a YYYYMM string and LinkedIn retires old ones on a schedule. If you hard-code a single version, your integration has an expiry date you did not choose. Keep a small ladder of acceptable versions in configuration, and make it loud when the newest one you know about is rejected.

The empty 201

This is the one that costs an afternoon.

POST /rest/posts succeeds with 201 Created and an empty body. There is no JSON, no id, nothing. A client written the obvious way parses the body, finds nothing, and reports a failure for a post that went out perfectly.

The id is in a response header:

x-restli-id: urn:li:share:7231...

Read it from there. And if the header is missing but the status was 201, the post still succeeded — treat the missing id as an unknown id, not as an error, because retrying will publish a duplicate.

A document is posted alone

LinkedIn will not accept a post carrying a PDF and images together. One document, nothing else.

This is worth validating before the upload rather than after. Otherwise you discover it at step three, having already pushed a 20MB file across the wire.

Polling, and the duplicate-post trap

The same arithmetic that applies to Instagram containers applies here, and it is worth repeating because the failure is identical and equally expensive.

If your scheduler reclaims a job it thinks is stuck after five minutes, and your document-processing poll runs for five minutes, the two race. A second worker claims the post while the first is still polling, and the customer's audience gets the same carousel twice.

Give the poll a budget that is a fraction of the reclaim timeout. Half is fine. The cost of giving up early is a failed post the user can retry; the cost of giving up late is a duplicate they cannot delete from everyone's feed.

Summary

Upload a PDF, wait for AVAILABLE, create the post, read the id from x-restli-id. Post the document on its own. Keep your API version in config, and keep your poll shorter than your reclaim.

Frequently asked questions

Does LinkedIn have a carousel API?

No. LinkedIn removed carousel ads as a native format and never offered a carousel endpoint for organic posts. The swipeable post you see in the feed is a PDF uploaded through the Documents API and rendered by LinkedIn as pages you can swipe.

Why is the response body empty when I create a LinkedIn post?

The versioned /rest/posts endpoint answers 201 Created with no body. The id of the created post is returned in the x-restli-id header, so a client that only reads the body will conclude the call failed.

Can a LinkedIn post contain both a PDF and images?

No. A document post carries exactly one document and nothing else. If you need both, they have to be separate posts.