# @kody/linkedin — agent notes Human setup and intent live in [`README.md`](./README.md). This file is for agents: imports, smoke/dryRun execute snippets, and edge cases. Auth is the saved OAuth integration named **`linkedin`** — never paste tokens. Do not disable live webhooks or jobs. No `/account/secrets/new` token path for this package. ## Auth - Integration id: `linkedin` (`createAuthenticatedFetch('linkedin')`) - API origin: `https://api.linkedin.com` - Scopes expected: `openid`, `profile`, `email`, `w_member_social` - Author URN comes from `/v2/userinfo` (`urn:li:person:{sub}`) unless overridden ## Import paths | Export | Import | | --- | --- | | overview / safety metadata | `kody:@kody/linkedin` | | smoke-test | `kody:@kody/linkedin/smoke-test` | | get-user-info | `kody:@kody/linkedin/get-user-info` | | get-person-urn | `kody:@kody/linkedin/get-person-urn` | | request | `kody:@kody/linkedin/request` | | create-post | `kody:@kody/linkedin/create-post` | | create-text-post | `kody:@kody/linkedin/create-text-post` | | create-article-post | `kody:@kody/linkedin/create-article-post` | | create-image-post | `kody:@kody/linkedin/create-image-post` | | create-video-post | `kody:@kody/linkedin/create-video-post` | | delete-post | `kody:@kody/linkedin/delete-post` | | register-image-upload | `kody:@kody/linkedin/register-image-upload` | | register-video-upload | `kody:@kody/linkedin/register-video-upload` | | upload-video-part | `kody:@kody/linkedin/upload-video-part` | | finalize-video-upload | `kody:@kody/linkedin/finalize-video-upload` | Prefer static `kody:@kody/linkedin/...` imports from `execute`. Do not lead with `packages.invoke`. For smoke against a **forked** package runtime that must bind the caller's OAuth grant, a dynamic invoke of bare kody id `linkedin` is acceptable — prefer the fork's published export once connected. ## Smoke test (read-only) ```js import smokeTest from 'kody:@kody/linkedin/smoke-test' export default async function main() { return await smokeTest() // => { ok: true, integration: 'linkedin', hasSubject: true, personUrnPresent: true } } ``` Identity helpers: ```js import getPersonUrn from 'kody:@kody/linkedin/get-person-urn' export default async function main() { return await getPersonUrn() } ``` ```js import request from 'kody:@kody/linkedin/request' export default async function main() { return await request({ path: '/v2/userinfo', dryRun: true }) } ``` ## dryRun / confirm (writes) Posting and upload helpers never mutate LinkedIn when `dryRun: true`. A live write requires a fresh `confirm: true` after the user approved the exact payload. ```js import createTextPost from 'kody:@kody/linkedin/create-text-post' export default async function main() { return await createTextPost({ text: 'Draft text', dryRun: true }) } ``` ```js import createArticlePost from 'kody:@kody/linkedin/create-article-post' export default async function main() { return await createArticlePost({ text: 'Read this', url: 'https://example.com', title: 'Example article', description: 'A useful article.', thumbnailUrl: 'https://example.com/thumbnail.jpg', dryRun: true, }) } ``` LinkedIn does not scrape Open Graph metadata for API-created article posts. Pass `title`, `description`, and either an existing LinkedIn image `thumbnail` URN or an HTTPS `thumbnailUrl`. Native video: `register-video-upload` → `upload-video-part` (each part) → `finalize-video-upload` → `create-video-post`. ## Edge cases / fork notes - Fork first; connect **the caller's** LinkedIn app. Do not treat the live `@kody/linkedin` integration as theirs. - No refresh token without LinkedIn partner access — on 401, reconnect at `/connect/oauth?provider=linkedin`. - HTTP 403 on posting: Share on LinkedIn product + `w_member_social`, then reconnect. - `redirect_uri` must be exactly `https://kody.codes/connect/oauth`. - Do not hard-code a member URN. Do not use this as an org-page poster. - Never treat smoke success as permission to post. - Keep `LICENSE` (MIT), `## Intent`, and `community-icon.svg` for community listing.