{"record":{"id":"3167a3fc50472ba4","repo":"zeroclaw-labs/zeroclaw","slug":"linkedin-image-register-failed-status-body-t","errorCode":null,"errorMessage":"LinkedIn image register failed ({status}): {body_text}","messagePattern":"LinkedIn image register failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/linkedin_client.rs","lineNumber":615,"sourceCode":"        person_id: &str,\n    ) -> anyhow::Result<String> {\n        let owner_urn = format!(\"urn:li:person:{person_id}\");\n\n        // Step 1: Register upload\n        let register_body = json!({\n            \"initializeUploadRequest\": {\n                \"owner\": owner_urn\n            }\n        });\n        let register_url = format!(\"{LINKEDIN_API_BASE}/rest/images?action=initializeUpload\");\n        let register_resp = self\n            .api_request(Method::POST, &register_url, token, Some(register_body))\n            .await?;\n\n        let status = register_resp.status();\n        if !status.is_success() {\n            let body_text = register_resp.text().await.unwrap_or_default();\n            anyhow::bail!(\"LinkedIn image register failed ({status}): {body_text}\");\n        }\n\n        let register_json: serde_json::Value = register_resp\n            .json()\n            .await\n            .context(\"Failed to parse image register response\")?;\n\n        let upload_url = register_json\n            .pointer(\"/value/uploadUrl\")\n            .and_then(|v| v.as_str())\n            .ok_or_else(|| {\n                ::zeroclaw_log::record!(\n                    ERROR,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                        .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                        .with_attrs(::serde_json::json!({\"missing\": \"uploadUrl\"})),\n                    \"linkedin_client: register response missing uploadUrl\"\n                );","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/linkedin_client.rs#L597-L633","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: person_id from a hardcoded/config value of another member -> 403\nclient.upload_image(&bytes, &token, &config_person_id).await?;\n\n// after: derive the owner from the token's own profile\nlet profile = client.get_profile().await?;\nclient.upload_image(&bytes, &token, &profile.id).await?;","handlingStrategy":"try-catch","validationCode":"// Owner must match the token: derive person_id from the same credentials\nlet profile = client.get_profile().await?; // uses same stored creds\n// pass profile.id as person_id to upload_image","typeGuard":null,"tryCatchPattern":"match client.upload_image(&bytes, &token, &person_id).await {\n    Ok(urn) => Ok(urn),\n    Err(e) if e.to_string().contains(\"(403\") => {\n        Err(e).context(\"image upload denied; check w_member_social scope and owner URN\")\n    }\n    Err(e) => Err(e),\n}","preventionTips":["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."],"tags":["linkedin","http-status","image-upload","oauth-scope"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}