# 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.
