zeroclaw-labs/zeroclaw · error
LinkedIn image register failed
Error message
LinkedIn image register failed ({status}): {body_text} What it means
Thrown in step 1 of upload_image when POST {LINKEDIN_API_BASE}/rest/images?action=initializeUpload returns non-2xx, body included. The registration body only carries the owner URN (urn:li:person:{person_id}), so failures are almost always scope/permission or owner mismatches rather than payload complexity. On success the response's uploadUrl is used for the binary PUT, so this guard fires before any bytes are uploaded.
Solutions
- For 403, ensure the app has the Share on LinkedIn product and the token has w_member_social.
- Get person_id from get_profile() using the same token you pass to upload_image — the owner URN must match the token owner.
- For 401, refresh the access token first.
- Verify the exact error in the response body; LinkedIn typically names the missing permission.
Example fix
// before: person_id from a hardcoded/config value of another member -> 403 client.upload_image(&bytes, &token, &config_person_id).await?; // after: derive the owner from the token's own profile let profile = client.get_profile().await?; client.upload_image(&bytes, &token, &profile.id).await?;
Defensive patterns
Strategy: try-catch
Validate before calling
// Owner must match the token: derive person_id from the same credentials let profile = client.get_profile().await?; // uses same stored creds // pass profile.id as person_id to upload_image
Try / catch
match client.upload_image(&bytes, &token, &person_id).await {
Ok(urn) => Ok(urn),
Err(e) if e.to_string().contains("(403") => {
Err(e).context("image upload denied; check w_member_social scope and owner URN")
}
Err(e) => Err(e),
} Prevention
- Always pair upload_image's token and person_id from the same get_profile call.
- Confirm the app includes the Share on LinkedIn product before shipping image features.
- Validate image bytes and MIME type locally first — size/type errors otherwise surface only at upload.
When it happens
Trigger: Token lacking w_member_social (403); person_id not matching the token owner so the owner URN is rejected (403/400); expired token (401); app not approved for image upload.
Common situations: Text posting works (token has scope) but images fail because the product/scope configuration differs; person id taken from a different profile than the authorized member; stale member ids cached after re-authorization.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- LinkedIn create_post failed
- LinkedIn add_comment failed
- LinkedIn add_reaction failed
- LinkedIn delete_post failed
- LinkedIn get_engagement failed
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3167a3fc50472ba4.
Report an issue: GitHub.
Appendix: source
Thrown at crates/zeroclaw-tools/src/linkedin_client.rs:615
person_id: &str,
) -> anyhow::Result<String> {
let owner_urn = format!("urn:li:person:{person_id}");
// Step 1: Register upload
let register_body = json!({
"initializeUploadRequest": {
"owner": owner_urn
}
});
let register_url = format!("{LINKEDIN_API_BASE}/rest/images?action=initializeUpload");
let register_resp = self
.api_request(Method::POST, ®ister_url, token, Some(register_body))
.await?;
let status = register_resp.status();
if !status.is_success() {
let body_text = register_resp.text().await.unwrap_or_default();
anyhow::bail!("LinkedIn image register failed ({status}): {body_text}");
}
let register_json: serde_json::Value = register_resp
.json()
.await
.context("Failed to parse image register response")?;
let upload_url = register_json
.pointer("/value/uploadUrl")
.and_then(|v| v.as_str())
.ok_or_else(|| {
::zeroclaw_log::record!(
ERROR,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({"missing": "uploadUrl"})),
"linkedin_client: register response missing uploadUrl"
);View on GitHub (pinned to 88bb9c8533)