# LinkedIn API posting errors, explained

When `POST /rest/posts` fails, LinkedIn answers with an HTTP status and a JSON body holding a `message`, a
`serviceErrorCode` and the `status`. The message is usually specific enough to act on, so read it first.

Every LinkedIn fact here comes from LinkedIn's own documentation (API version 202610), checked on October 26, 2026: its
[error handling](https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/error-handling),
[Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-10),
[sign-in flow](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow),
[refresh tokens](https://learn.microsoft.com/en-us/linkedin/shared/authentication/programmatic-refresh-tokens),
[rate limits](https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/rate-limits),
[versioning](https://learn.microsoft.com/en-us/linkedin/marketing/versioning?view=li-lms-2026-10),
[Images API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/images-api?view=li-lms-2026-10),
[Videos API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/videos-api?view=li-lms-2026-10)
and [Documents API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/documents-api?view=li-lms-2026-10)
pages, and the error table of the legacy
[UGC Post API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/ugc-post-api?view=li-lms-2026-10).
LinkedIn ships a new API version every month, so check the linked page before you build on it.

## The short answer

| LinkedIn answers | What it means | What fixes it |
|---|---|---|
| 401, expired or invalid access token | The token is past its 60 days, or wrong | Refresh it if you have a refresh token; otherwise sign the member in again |
| 401, the token has been revoked | The member removed your app on LinkedIn | Sign the member in again |
| 403 `ACCESS_DENIED` | The token lacks the permission, or the member lacks the page role | Add the permission, then get a new token |
| 422, "Content is a duplicate of …" | An identical post was made recently | Wait 10 minutes, or change the text |
| 422 `UNPROCESSABLE_ENTITY` | The request is well-formed, but its values don't fit together | Read the message and fix the field it names |
| 400 `MISSING_FIELD`, `FIELD_LENGTH_TOO_LONG` and similar | A field is missing, too long or the wrong type | Fix the field the message names |
| 426 | The `Linkedin-Version` header names a retired version | Send a current version |
| 429 | A daily limit | Wait for the reset at midnight UTC |
| Media status `WAITING_UPLOAD` or `PROCESSING` | The file isn't ready yet | Wait until it's `AVAILABLE`, then post |
| Media status `PROCESSING_FAILED` | LinkedIn couldn't process the file | Fix the file and upload it again |
| 500, 503 or 504 | A problem on LinkedIn's side | Check the profile for the post, then retry |

## 401: expired, invalid or revoked tokens

LinkedIn's error handling page lists the 401s:

| Message | What fixes it |
|---|---|
| Unknown authentication schema | Send `Authorization: Bearer <access token>` |
| Empty OAuth2 access token (`EMPTY_ACCESS_TOKEN` in the Posts API) | The header is missing or empty: send the token |
| Invalid access token | Get a correct token through the sign-in flow |
| Expired access token | Refresh the token |
| The token has been revoked | The member revoked your app in their LinkedIn settings, so they sign in again |

How long tokens last:

- **Access tokens last 60 days.** `expires_in` gives the seconds left (5,184,000 for a new token). LinkedIn doesn't issue
  longer-lived ones.
- **Refresh tokens are for approved partners.** LinkedIn offers programmatic refresh tokens to approved Marketing
  Developer Platform partners. One lasts a year from the first sign-in, and each refresh gives a new 60-day access token
  without restarting that year. When it ends, the member signs in again.
- **Without a refresh token**, as with the self-serve Share on LinkedIn product, you renew by sending the member through
  sign-in again. LinkedIn skips the consent screen when the member is still signed in to linkedin.com and their current
  token hasn't expired, so renew before day 60.
- **New scopes end old tokens.** Requesting a different set of permissions than the member granted before invalidates
  every earlier access token.
- **Some token failures are 500s.** LinkedIn says a failure while it verifies the token returns 500, not 401.

## 403: missing permissions

`ACCESS_DENIED` means the token doesn't carry the permission the call needs, or the member doesn't have the role it
needs. LinkedIn's Posts API names the permissions: `w_member_social` to post as the member, `w_organization_social` to
post as a company page, which also takes the member's role on that page. Check, in order:

- **The app has the product.** `w_member_social` comes with the self-serve Share on LinkedIn product;
  `w_organization_social` with the vetted Community Management API
  ([what each permission covers](/docs/guides/linkedin-api-personal-profile)).
- **The member granted it.** A permission has to be in the sign-in request. If you added it after the member signed in,
  sign them in again for a token that has it. LinkedIn's checklist asks whether your app changed the scope when it
  requested the token.
- **The author is the member.** Post as `urn:li:person:{id}` for the member whose token you use. Their photos, videos and
  documents must belong to them too: LinkedIn checks that the caller matches the media's owner.
- **The call isn't a read.** LinkedIn's Images API says `w_member_social` is write-only: a token with only that
  permission can't make versioned `GET /rest/images` calls and gets 403 "Accessing this image resource is forbidden".
  The Documents API's equivalent is `DOCUMENT_FORBIDDEN`.

LinkedIn also says some restricted APIs answer 404 rather than 403.

## 400 and 422: content errors

The Posts API's error table:

| Status | Code | What it means |
|---|---|---|
| 400 | `MISSING_FIELD` | A required field, such as `author`, `visibility`, `distribution` or `lifecycleState`, is missing |
| 400 | `INVALID_VALUE_FOR_FIELD` | A value isn't allowed, such as an unknown `visibility`, `feedDistribution` or `lifecycleState` |
| 400 | `FIELD_LENGTH_TOO_LONG` | `commentary` or another text field is too long |
| 400 | `INVALID_VALUE_BLANK_FIELD` | A required text field is empty |
| 400 | `INVALID_URN_TYPE` or `INVALID_URN_ID` | A URN, often `author` or `content.media.id`, is the wrong type or malformed |
| 409 | `CONFLICT` | A write conflict: retry |
| 422 | `UNPROCESSABLE_ENTITY` | Well-formed, but the values don't fit together |

**Duplicates.** LinkedIn documents the duplicate refusal in its legacy UGC Post API's error table: 422 "Content is a
duplicate of {share or UGC URN}", meaning an exact duplicate of a recently created post. Wait 10 minutes for the
restriction to end, or change the content. The newer Posts API's table lists only the general 422. The same UGC table
lists 422 "Error validating the post", whose message gives the reason, such as a missing or too-long field.

## Media upload states

Photos, videos and documents are uploaded before the post, and each has a status. Post only once it's `AVAILABLE`:

| Status | What it means | What to do |
|---|---|---|
| `WAITING_UPLOAD` | LinkedIn is waiting for the file, or for its upload to finish | Finish the upload; a video also needs its finalize call |
| `PROCESSING` | LinkedIn is still preparing it | Check again later, and don't post yet |
| `AVAILABLE` | It's ready | Post it |
| `PROCESSING_FAILED` | A file too large, an unsupported format or an error on LinkedIn's side | Fix the file and upload it again; a video's `processingFailureReason` says why |

- **Upload URLs expire.** Each upload URL comes with an expiry. Video upload URLs typically last 30 days from the start of
  the upload; uploading to an expired one answers 401, and the Videos API also lists 400 `EXPIRED_UPLOAD_URL`.
- **Video parts.** Upload each part to the URLs LinkedIn returns, keep each part's `ETag` response header, then finalize
  with the `uploadToken` and those IDs in order.
- **Posting too soon.** A post whose video is still waiting, or failed, is refused with 400 `MEDIA_ASSET_WAITING_UPLOAD`
  or `MEDIA_ASSET_PROCESSING_FAILED`.

LinkedIn's limits for each kind:

| Media | LinkedIn's limit |
|---|---|
| Photos | JPG, GIF or PNG, under 36,152,320 pixels; GIFs up to 250 frames |
| Video | MP4, 3 seconds to 30 minutes, 75 KB to 500 MB |
| Documents | PDF, PPT, PPTX, DOC or DOCX, up to 100 MB and 300 pages |

## 429: rate limits

LinkedIn counts calls per 24 hours, resetting at midnight UTC, with one limit for your app and one for each member
using it. It doesn't publish the standard limits: your app's Analytics tab in the Developer Portal shows them for each
endpoint you've called. Its [Share on LinkedIn guide](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin)
lists 150 requests per member and 100,000 per app each UTC day for its legacy `/v2/ugcPosts` flow; it doesn't establish
those as the current `/rest/posts` or media endpoint quotas. The 429's message can read "Resource level throttle limit
for calls to this resource is reached."

- **Wait for the reset,** and cut repeated calls.
- **A share limit.** The legacy UGC Post table also lists 429 "UGC action was blocked because a share limit has been
  reached": the author hit a daily limit on posts.
- **Protective 429s.** LinkedIn sometimes returns a 429 to protect its systems; it clears by itself.
- **Alerts.** Developer admins get an email when the app passes 75% of a limit, about one to two hours late.

## 426: an old API version

The versioned `/rest` API needs a `Linkedin-Version: YYYYMM` header. LinkedIn releases a version every month and
supports each for at least a year. A retired version gets 426, and a request without the header gets an error too.
Version 202511 is retired on November 16, 2026. Move the header to a current version, then check what changed since.

## 500, 503 and 504

These are LinkedIn's side: retry with backoff. If 500s keep coming, LinkedIn asks for the request, the response headers
(`x-li-uuid`, `x-li-fabric` and `x-li-request-id`) and your app's client ID in a support request. Before you retry
creating a post, look at the profile: an error answer doesn't prove the post wasn't made.

## Posting through Flaghand

Flaghand's check stops several of these before anyone approves a post: text over 3,000 characters counted the way
LinkedIn's post box counts, more than 20 photos, alt text over 4,086 characters, a video that isn't a single MP4 of 75 KB
to 500 MB and 3 seconds to 30 minutes, a PDF over 300 pages, a company tag without its page's exact name and ID, and a
login that runs out before the post time ([draft rules](https://app.flaghand.com/docs/drafts-and-media)). Flaghand's uploads take
files up to 50 MB.

After the owner approves it:

- **Media waits for `AVAILABLE`.** Flaghand uploads a video or PDF, checks it each minute while LinkedIn processes it,
  and posts only once it's `AVAILABLE`. `PROCESSING_FAILED` ends the job without a post.
- **The job shows LinkedIn's answer,** such as `LinkedIn returned HTTP 422:` followed by LinkedIn's message.
- **A 429 fails visibly, without changing the approved time.** It won't be sent again automatically. Choose a new time
  after LinkedIn allows requests again and request approval. Flaghand shows a valid reset hint when LinkedIn supplies
  one, but it isn't a guarantee of capacity.
- **A 5xx on the post itself isn't resent,** since LinkedIn may have made it. The job asks you to look on the profile
  and approve it again only if the post isn't there.
- **A 401 holds the job.** The owner reconnects LinkedIn in Settings, then retries the post in the desk. LinkedIn
  logins last 60 days, and the desk flags one a week before it ends.
- **No read-back.** Flaghand can't read LinkedIn posts back, so "posted" means LinkedIn accepted it.

Flaghand's own API answers with its own [error codes](https://app.flaghand.com/docs/errors).

[Get access to Flaghand](/schedule).

[All guides](/docs/guides.md)
