{"record":{"id":"0acdd8d05ad52f6a","repo":"zeroclaw-labs/zeroclaw","slug":"linkedin-create-post-failed","errorCode":null,"errorMessage":"LinkedIn create_post failed ({}): {}","messagePattern":"LinkedIn create_post failed \\((.+?)\\): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/linkedin_client.rs","lineNumber":295,"sourceCode":"                \"content\".to_string(),\n                json!({\n                    \"article\": {\n                        \"source\": url,\n                        \"title\": article_title.unwrap_or(\"\")\n                    }\n                }),\n            );\n        }\n\n        let url = format!(\"{}/rest/posts\", LINKEDIN_API_BASE);\n        let response = self\n            .api_request(Method::POST, &url, &creds.access_token, Some(body))\n            .await?;\n\n        let status = response.status();\n        if !status.is_success() {\n            let body_text = response.text().await.unwrap_or_default();\n            anyhow::bail!(\"LinkedIn create_post failed ({}): {}\", status, body_text);\n        }\n\n        // The post URN is returned in the x-restli-id header\n        let post_urn = response\n            .headers()\n            .get(\"x-restli-id\")\n            .and_then(|v| v.to_str().ok())\n            .map(String::from)\n            .unwrap_or_default();\n\n        Ok(post_urn)\n    }\n\n    pub async fn list_posts(&self, count: usize) -> anyhow::Result<Vec<PostSummary>> {\n        let creds = self.get_credentials().await?;\n        let author_urn = format!(\"urn:li:person:{}\", creds.person_id);\n        let url = format!(\n            \"{}/rest/posts?author={}&q=author&count={}\",","sourceCodeStart":277,"sourceCodeEnd":313,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/linkedin_client.rs#L277-L313","documentation":"Thrown when POST to LinkedIn's /rest/posts returns a non-2xx status. The full response body is included (not truncated), which matters because LinkedIn returns detailed JSON errors. The post URN is taken from the x-restli-id header on success, so any failure status aborts before that.","triggerScenarios":"403 when the token lacks the w_member_social scope; 400 when the author URN is wrong or text/visibility fields are malformed; 401 when the access token is expired and refresh did not happen or failed; 429 rate limit on posts creation.","commonSituations":"App configured with only r_liteprofile/r_basicprofile scopes instead of the LinkedIn API 'Share on LinkedIn' (w_member_social) product; person URN from a different member than the token owner; access tokens older than 60 days; missing refresh token so api_request could not renew.","solutions":["Read the status: 403 => add the w_member_social scope (Share on LinkedIn product) to the app and re-authorize; 401 => refresh or re-issue the token; 400 => fix the payload; 429 => back off.","Ensure the author field is urn:li:person:{id} of exactly the member who authorized the token.","Check that text is non-empty and visibility is 'PUBLIC' or 'CONNECTIONS' as the API requires.","If posting as an organization, use urn:li:organization:{id} with the matching admin permissions."],"exampleFix":"// before: posting with token that lacks w_member_social -> 403\nclient.create_post(&token, &person_urn, \"Hello\").await?;\n\n// after: request the right scopes during OAuth, then post\n// scopes: \"openid profile w_member.social\" (or legacy w_member_social)\nclient.create_post(&fresh_token, &format!(\"urn:li:person:{person_id}\"), \"Hello\").await?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match client.create_post(&token, &author_urn, text).await {\n    Ok(urn) => Ok(urn),\n    Err(e) => {\n        let m = e.to_string();\n        if m.contains(\"(403\") { Err(e).context(\"missing w_member_social scope; re-authorize\") }\n        else if m.contains(\"(401\") { Err(e).context(\"token expired; refresh\") }\n        else { Err(e) }\n    }\n}","preventionTips":["Request w_member_social (plus openid profile) at authorization time; verify scopes before first post.","Keep the refresh token stored so api_request can renew expired access tokens.","Build the author URN from get_profile().id of the same member who owns the token."],"tags":["linkedin","http-status","create-post","oauth-scope"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}