I'm having some difficulty with the image upload feature, and I'm hoping somebody already knows the answer... I'm following these steps:
POST
to initialise an upload (and get the upload URL)PUT
the image to the upload URLGET
the status of the image (polling until either AVAILABLE
or PROCESSING_FAILED
)content.media
or content.multiImage
in a post.I reliably get PROCESSING_FAILED
for the image status, so I'm halted at step 3. I'll share some more detail below, and I'd be glad of anyone's insights... Thanks!
I'm working in C# with the RestSharp library, but it should be pretty clear what I'm doing, and so probably doesn't matter which language you're familiar with if you can see the error in my method...
My app has been granted access to the Community Management API (development tier), and I'm using a token with w_member_social
and w_organization_social
scopes.
I own the page that I'm trying to post to, and the organisation urn is correct for that page.
I'm working with these documentation pages:
Where possible, I've simplified the code below by inlining strings, etc. so it's a little easier to see what I'm doing...
First, I initialise the client with the most commonly used base url:
var client = new RestClient("https://api.linkedin.com/rest");
var imgRequest = new RestRequest($"images?action=initializeUpload", Method.Post);
imgRequest.AddHeader("Authorization", $"Bearer {token}");
imgRequest.AddHeader("Content-Type", "application/json");
imgRequest.AddHeader("X-Restli-Protocol-Version", "2.0.0");
imgRequest.AddHeader("LinkedIn-Version","202309");
var imgRequestBody = new { initializeUploadRequest = new { owner = author }};
imgRequest.AddJsonBody(imgRequestBody);
var imgResponse = await client!.ExecuteAsync(imgRequest);
This returns a success response and I can extract the URL from the JSON:
var imgResponseData = JsonConvert.DeserializeObject<LinkedInImageResponse>(imgResponse.Content!);
var uploadUrl = imgResponseData!.value.uploadUrl;
using (var uploadClient = new RestClient())
{
var uploadRequest = new RestRequest(uploadUrl, Method.Put); // PUT is correct
uploadRequest.AddHeader("Authorization", $"Bearer {token}");
uploadRequest.AddHeader("Content-Type", "image/png"); // TODO: get this from the image
// uploadRequest.AddHeader("X-Restli-Protocol-Version", "2.0.0");
// uploadRequest.AddHeader("LinkedIn-Version","202309");
var imgUploadStream = await image.GetStreamAsync();
uploadRequest.AddFile($"image_{++iCounter}", () => imgUploadStream, image.Filename);
var uploadResponse = await uploadClient.ExecuteAsync(uploadRequest);
if (!uploadResponse.IsSuccessful) throw new Exception(); // simplified
}
This returns a success response, and so I believe the image has uploaded successfully.
This is where things start to go a bit awry...
var imageOk = false;
do
{
Thread.Sleep(5000); // 5s poll... (ugh!)
var pollRequest = new RestRequest($"images/{imgResponseData!.value.image}", Method.Get);
pollRequest.AddHeader("Authorization", $"Bearer {token}");
pollRequest.AddHeader("LinkedIn-Version","202309");
var pollResponse = await client!.ExecuteAsync(pollRequest);
var pollData = JsonConvert.DeserializeObject<LinkedInPollResponse>(pollResponse.Content!);
imageOk = pollData?.status == "AVAILABLE";
var imageFailed = pollData?.status == "PROCESSING_FAILED";
if (imageFailed) { throw new Exception($"Image status for post {t}: {pollData?.status}", new Exception(Summarise(pollRequest, null) + Summarise(pollResponse))); }
if (++attempts > 5) throw new Exception($"Cannot poll image status for post {t} - too many attempts");
} while (!imageOk);
This uses the common client again, with the base url initialised in step 0.
I get a 200
(success) response for the request, and the content contains status PROCESSING_FAILED
:
{
"owner":"urn:li:organization:19321500",
"status":"PROCESSING_FAILED",
"id":"urn:li:image:D4E10AQHZqWNBn_7gOQ"
}
The documentation says:
PROCESSING_FAILED - Processing failed due to client error such as file size too large, unsupported file format, internal error (e.g., performance issue, database error, network failure), or other issue.
The image is a small (291kb) PNG dimensions: 432x430. I'd be surprised if there was anything wrong with it - it renders fine under ordinary circumstances (including when manually creating a post).
I'd be grateful for any insights...
Having rubber ducked the whole thing rather intensely, I tried a few more keywords in my search and found this solution from j4rey, which answers the problem.
TLDR: The issue is that I had included the file using uploadRequest.AddFile
- which includes it as a part of a form post. LinkedIn wants the file as binary content.
Here's my adaptation of their code - which works perfectly:
using (var uploadClient = new HttpClient())
{
uploadClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var imageDataStream = await image.GetStreamAsync();
var uploadContent = new StreamContent(imageDataStream);
var uploadResponse = await uploadClient.PutAsync(uploadUrl, uploadContent);
if (!uploadResponse.IsSuccessStatusCode) throw new Exception($"Cannot upload image for post {t}", new Exception(Summarise(uploadResponse)));
}
(NB. I switched away from RestSharp for the upload - it's not really a restful action anyway, and HttpClient
is a familiar friend.)