---
name: linkedin-upload-media-and-post
description: Run LinkedIn's three-phase media upload (initialize, PUT the bytes to the returned upload URL, verify the asset) for images, documents and video, then publish a post that references the resulting URN.
api: openapi/linkedin-use-cases-image-ad-api-openapi.yml
apis:
  - openapi/linkedin-use-cases-image-ad-api-openapi.yml
  - openapi/linkedin-use-cases-document-ad-api-openapi.yml
  - openapi/linkedin-use-cases-video-ad-api-openapi.yml
operations:
  - postInitializeImageUpload
  - getGetASingleImage
  - postInitializeDocumentUpload
  - putUploadTheDocumentFile
  - postCreateDocumentContent
  - postInitializeVideoUpload
  - getGetASingleVideo
generated: '2026-08-13'
method: generated
source: openapi/*.yml, arazzo/linkedin-upload-image-and-create-post-workflow.yml, arazzo/linkedin-upload-document-and-create-post-workflow.yml, arazzo/linkedin-upload-video-and-verify-workflow.yml
---

# Upload media and publish a rich post

LinkedIn never accepts media bytes on the post-creation call. Media is always a separate
three-phase flow, and the post only ever references the asset URN.

## Headers

`Authorization: Bearer {token}`, `Linkedin-Version: {YYYYMM}`, `X-Restli-Protocol-Version: 2.0.0`
on every `api.linkedin.com/rest/` call. The byte upload in phase 2 goes to the URL LinkedIn hands
back, which is **not** on `api.linkedin.com` and takes only the upload headers LinkedIn specifies.

## The three phases

### Images
1. `postInitializeImageUpload` — returns an `uploadUrl` and an image URN.
2. `PUT` the raw bytes to that `uploadUrl`.
3. `getGetASingleImage` — poll until the asset is available. Do not publish against an
   unverified image URN; the post will be created with a broken asset reference.

### Documents
1. `postInitializeDocumentUpload` — returns the upload URL and a document URN.
2. `putUploadTheDocumentFile` — uploads the file.
3. `postCreateDocumentContent` — creates the post that carries the document.

Fetch back with `getGetASingleDocument` / `getGetMultipleDocuments` /
`getFetchMultipleDocumentContent` when the agent needs to confirm or list what exists.

### Video
1. `postInitializeVideoUpload` — returns upload instructions (video uploads are chunked; follow the
   part list LinkedIn returns rather than assuming a single PUT).
2. Upload the parts.
3. `getGetASingleVideo` — verify. Video processing is asynchronous; a URN existing is not the same
   as a video being playable, so poll until the asset reports ready before publishing.
   `getGetMultipleVideos` batches the check.

## Rules an agent must follow

- **Verify before you publish.** Every media family has a read operation for exactly this reason.
  Skipping it produces posts that render as broken attachments and cannot be fixed by editing.
- **Never retry an initialize on a timeout.** There is no idempotency key
  (`conventions/linkedin-conventions.yml`); a retried initialize allocates a second asset and leaks
  a half-uploaded artifact. Re-read first.
- The upload URL is short-lived. If the byte PUT fails after the URL expires, restart at phase 1 —
  do not reuse the old URN.
- 429 means the daily application or member ceiling was hit and resets at 00:00 UTC
  (`rate-limits/linkedin-rate-limits.yml`).
- Errors carry `{status, message, serviceErrorCode}`, not `application/problem+json`.
