Glossary

How LinkedIn initializeUpload Turns a File Into an Image URN

Taras Shynkarenko
Taras Shynkarenko
Updated: 8 min read
How LinkedIn initializeUpload turns a file into an image URNHow LinkedIn initializeUpload turns a file into an image URN

TL;DR, Quick Answer

8 min read

Posting an image to LinkedIn takes three calls. POST /rest/images?action=initializeUpload registers the upload and returns an uploadUrl, an uploadUrlExpiresAt timestamp and an image URN in the form urn:li:image:{id}. You then send the file to that URL, which the Assets page documents as a PUT carrying an OAuth token, and reference the URN as content.media.id on POST /rest/posts. Each step has its own error table, and LinkedIn documents no retry path for a failed upload beyond starting over.

What does the LinkedIn initializeUpload call do?

A LinkedIn initializeUpload call registers an image upload before a single byte moves, and hands back the URL to send the file to plus the URN the post will reference. LinkedIn's own description is three sentences: "Use the initializeUpload action to register the upload. When you initialize, you declare the upcoming upload. Use the upload URL to upload the image."

The call is an action on the Images API, passed as a query parameter:

POST https://api.linkedin.com/rest/images?action=initializeUpload
Authorization: Bearer {INSERT_TOKEN}
Linkedin-Version: 202608
X-Restli-Protocol-Version: 2.0.0

{
  "initializeUploadRequest": {
    "owner": "urn:li:organization:5583111"
  }
}

One required field, initializeUploadRequest.owner, described as the "URN of the entity that owns this asset. Can be a person(urn:li:person:123), or organization(urn:li:organization:123) URN." Worth reading that against the image schema itself, where the top-level owner field also accepts a sponsoredAccount URN. The initialize request lists only person and organization.

The optional second field registers the asset in an ad account's media library at the same time:

{
  "initializeUploadRequest": {
    "owner": "urn:li:organization:2414183",
    "mediaLibraryMetadata": {
      "associatedAccount": "urn:li:sponsoredAccount:123456789",
      "assetName": "My media library asset"
    }
  }
}

mediaLibraryMetadata.mediaLibraryStatus "defaults to ACTIVE on creation," so a library asset is live the moment it finishes processing.

One note the documentation puts in a callout, because it breaks a pattern people carry over from the older Assets API: "SYNCHRONOUS_UPLOAD is not supported in Images API." There is no single-call shortcut. The three steps are the whole surface.

What does initializeUpload return?

A 200 with three values, wrapped in value:

{
   "value": {
       "uploadUrlExpiresAt": 1650567510704,
       "uploadUrl": "https://www.linkedin.com/dms-uploads/C4E10AQFoyyAjHPMQuQ/uploaded-image/0?ca=vector_ads&cn=uploads&sync=0&v=beta&ut=08zHQjMjAOLqc1",
       "image": "urn:li:image:C4E10AQFoyyAjHPMQuQ"
   }
}

The image value is the URN, and it is the piece to store. Everything downstream refers to the image by that string: the post body, the GET that checks processing status, the media library listing. Notice that the identifier inside the URN is the same identifier inside the upload URL path, which makes the two easy to correlate in logs.

uploadUrlExpiresAt is milliseconds since the epoch. LinkedIn does not publish how long the window is, so the correct thing to do is read the timestamp rather than assume a duration. A queue that initializes a batch of uploads hours before it sends the files is depending on a number nobody has documented.

The image URN also has a shape worth validating on your side, since the Images API rejects malformed ones with a dedicated error. urn:li:image: followed by the identifier, and nothing else. The Images API accepts "Images with less than 36,152,320 pixels" in "JPG, GIF, and PNG formats," with "GIF format supports up to 250 frames."

A phone screen showing a file upload in progress, the step where image bytes move to the URL from initializeUpload.

How do you upload the image bytes?

To the uploadUrl, with the file as the body and the same bearer token attached. The Assets page that the Images API points at for this step is explicit about the method: "Use the uploadUrl from the previous step to upload the image. Use a PUT method to upload the image. The upload call requires a valid OAuth token in the 'Authorization' header. This is different than the upload video call which doesn't accept an OAuth token."

curl -i --upload-file ~/Desktop/Myimage.jpg \
  -H 'Authorization: Bearer Redacted' \
  "https://www.linkedin.com/dms-uploads/C5622AQHdBDflPp0pEg/feedshare-uploadedImage/0?ca=vector_feedshare&cn=uploads&sync=1&v=beta&ut=1lrKqjt4fYuqw1"

A successful upload answers HTTP/2 201 with content-length: 0. No body, no JSON, nothing to parse. The only thing you learn is the status code.

LinkedIn contradicts itself on the verb, and it is worth knowing before you debug a 405. The consumer Share on LinkedIn guide, which covers the older /v2/assets flow, says to "send a POST request to the uploadUrl with your image or video included as a binary file," and then demonstrates it with curl -i --upload-file, which is a PUT. The Assets page says PUT in prose and shows the same command. The command is the reliable part of both pages.

After the upload, GET https://api.linkedin.com/rest/images/urn:li:image:C4E10AQFn10iWtKexVA returns the asset with a status field. The documented values are WAITING_UPLOAD ("Waiting for client to upload source file or uploading process to be completed"), PROCESSING, AVAILABLE ("All of the recipe's required artifacts are ready. The asset is available to be served"), and PROCESSING_FAILED, which the schema attributes to "client error such as file size too large, unsupported file format, internal error."

AdaptlyPost
AdaptlyPost

Start 7-Day FREE Trial

All-platform analytics

Social Inbox

AI-powered assistant

One permission trap on that GET: the Images API requires rw_ads, w_member_social, w_organization_social or w_power_creators, and LinkedIn notes that "w_member_social permission are write-only and tokens with only w_member_social permissions would be unable to perform a GET call for rest/images." A member-scoped integration can upload and cannot poll.

How do you reference the image URN in a post?

As content.media.id on the Posts API, alongside the alt text:

POST https://api.linkedin.com/rest/posts

{
  "author": "urn:li:organization:5515715",
  "commentary": "test strings!",
  "visibility": "PUBLIC",
  "distribution": {
    "feedDistribution": "MAIN_FEED",
    "targetEntities": [],
    "thirdPartyDistributionChannels": []
  },
  "content": {
    "media": {
      "altText": "testing for alt tags",
      "id": "urn:li:image:C5610AQFj6TdYowm17w"
    }
  },
  "lifecycleState": "PUBLISHED",
  "isReshareDisabledByAuthor": false
}

"A successful response returns a 201 Created HTTP status code and the ID in the x-restli-id response header." The post URN comes back in a header, not in the body, and it looks like urn:li:share:6844785523593134080 or urn:li:ugcPost:68447855235931240. Integrations that only read response bodies lose the post ID entirely.

The altText on that media object carries its own documented ceiling, covered in the breakdown of where LinkedIn writes down its alt text limit. For a multi-image post the same URNs go into an images array instead, with a minimum of 2 and a maximum of 20.

A person reading an error message on a laptop screen, reflecting the debugging that follows a failed upload or post call.

Which errors show up at each step?

Three steps, three separate error tables, and they do not overlap.

StepStatusCodeWhat it means
initializeUpload400INVALID_URN_TYPE"{field} value {value} must be a {urnType} URN"
initializeUpload400INVALID_URN_ID"This URN ID is invalid"
initializeUpload403none published"Accessing this image resource is forbidden. Please check your permissions for this resource"
initializeUpload400VERSION_MISSINGThe version header was left off the request
Upload PUT401UNAUTHORIZED"The OAuth token is missing, invalid, or expired"
Upload PUT413REQUEST_ENTITY_TOO_LARGE"The uploaded file exceeds the allowed size limit"
Upload PUT415UNSUPPORTED_MEDIA_TYPE"The uploaded file format is not supported"
Upload PUT422UNPROCESSABLE_ENTITY"The server understands the request but can't process it"
POST /rest/posts400INVALID_URN_TYPE"Verify the URN type used for fields such as author or content.media.id"
POST /rest/posts400MISSING_FIELDauthor, visibility, distribution or lifecycleState absent
POST /rest/posts403ACCESS_DENIEDScope granted but the member lacks the company page role
POST /rest/posts429TOO_MANY_REQUESTS"The API rate limit has been exceeded"

The 403 on initialize is the one to read carefully, because LinkedIn publishes it as a raw body rather than a code, and because its cause is usually a page role rather than a scope. The documented permission checks are role-based: "For images with company URN owners, the caller must have ADMIN or DSC permissions for the company page," and "For images with member URN owners, the caller must match the image owner."

A missing version header fails the initialize call before any of that is evaluated, with 400 VERSION_MISSING and the message "A version must be present. Please specify a version by adding the Linkedin-Version header." The rules for that header, including what a sunset value returns, are worth reading alongside this flow in the piece on the version header every /rest/ call needs.

What does LinkedIn leave undocumented here?

Three things, and each one is a decision you have to make without a citation.

How long the upload URL lasts. You get uploadUrlExpiresAt in the response and no stated duration anywhere on the page, so a scheduler that batches uploads has to treat the timestamp as the contract.

Whether you must wait for AVAILABLE before creating the post. The status values are documented, the GET that returns them is documented, and the relationship between the two and the POST /rest/posts call is not. The safe pattern is to poll until AVAILABLE, and it is a pattern rather than a rule.

What to do about PROCESSING_FAILED. The schema names the causes and stops there. No retry endpoint is documented, which in practice means initializing a new upload and getting a new URN. Teams running this at volume through a LinkedIn post scheduler end up building that retry themselves, the same way they do for other publishing APIs with three-step media flows.

Three undocumented decisions
1
Upload URL lifetime. No duration is published, so treat uploadUrlExpiresAt as the only guarantee.
2
Waiting for AVAILABLE. Polling until the status clears is a pattern, not a documented rule.
3
PROCESSING_FAILED. No retry endpoint exists, so the fix is a new initializeUpload call and a new URN.
Each of these three decisions gets made without a citation from LinkedIn's docs.

Frequently asked questions

What is the LinkedIn initializeUpload endpoint?

POST https://api.linkedin.com/rest/images?action=initializeUpload. It is an action on the Images API that registers an upload and returns an uploadUrl, an uploadUrlExpiresAt timestamp and an image URN, before any file data is sent.

What does the image URN from initializeUpload look like?

urn:li:image:{id}, for example urn:li:image:C4E10AQFoyyAjHPMQuQ. The same identifier appears inside the returned upload URL path, and the URN is what you pass as content.media.id when you create the post.

Which HTTP method uploads the image to LinkedIn?

PUT. The Assets page states "Use a PUT method to upload the image" and requires "a valid OAuth token in the 'Authorization' header," and a successful upload returns 201 with an empty body. The consumer guide's prose says POST, but its own curl sample uses --upload-file, which sends a PUT.

AdaptlyPost
AdaptlyPost

Start 7-Day FREE Trial

All-platform analytics

Social Inbox

AI-powered assistant

Do you need to wait for the image to finish processing before posting?

LinkedIn does not document a required wait. It documents a status field on the image with the values WAITING_UPLOAD, PROCESSING, AVAILABLE and PROCESSING_FAILED, and polling GET /rest/images/{urn} until AVAILABLE is the safe reading of that.

Why does initializeUpload return 403?

The documented body is "Accessing this image resource is forbidden. Please check your permissions for this resource" with "status": 403. The permission checks are role-based: a company URN owner requires ADMIN or DSC permissions on the page, and a member URN owner must match the caller.

Can a token with only w_member_social use the Images API?

For writes, yes. LinkedIn states that "w_member_social permission are write-only and tokens with only w_member_social permissions would be unable to perform a GET call for rest/images," so such a token can initialize and upload but cannot poll the image status on the versioned endpoint.

What image formats and sizes does the LinkedIn Images API accept?

The Images API accepts JPG, GIF, and PNG files, capped at 36,152,320 pixels. GIF files can run up to 250 frames. These limits apply to any image sent through the three-step flow, not just one step of it.

Does the LinkedIn Images API support synchronous upload?

It does not. LinkedIn spells this out in a callout: "SYNCHRONOUS_UPLOAD is not supported in Images API." The three calls, initializeUpload, the PUT, and POST /rest/posts, are the whole surface, with no single-call shortcut like the pattern some carry over from the older Assets API.

Which OAuth scopes let you call the LinkedIn Images API?

rw_ads, w_member_social, w_organization_social, or w_power_creators. Any one of the four covers initializeUpload and the upload PUT, but w_member_social alone cannot read image status back with a GET, since LinkedIn documents that scope as write-only.

How many images can one LinkedIn post include?

A single-image post references one URN through content.media.id. A multi-image post swaps that for an images array, which LinkedIn requires to hold a minimum of 2 and a maximum of 20 URNs.

Was This Article Helpful?

Let us know what you think!

See us more often in Google

One click marks AdaptlyPost as a preferred source, so our articles sit higher in your Top Stories, AI Mode, and AI Overviews.

Before you go...

AdaptlyPost

AdaptlyPost

Schedule your content across all platforms

Manage all your social media accounts in one place with AdaptlyPost.

All-platform analytics

Social Inbox

AI-powered assistant

Related Glossary Terms

Related Articles