Identity, generic requests, and guarded posting through the saved LinkedIn OAuth integration.
- Other
- social
- oauth
- profile
- posting
- License
- MIT
- Published
- August 22, 2026
- Pinned commit
9c2b73c- Rating
- No ratings yet
- Forks
- 2
- Stars
- 0
- Adaptation effort
- —
README
@kody/linkedin
Official LinkedIn “in” mark in white on LinkedIn Blue #0A66C2 (brand guidelines / Simple Icons letter paths; enclosing square omitted so the mark is not inverted). LinkedIn® is a trademark of LinkedIn Corporation. This package is not affiliated with or endorsed by LinkedIn.
Intent
Give any Kody account reusable, headless LinkedIn helpers for identity (/v2/userinfo and the posting personUrn), a generic authenticated request escape hatch, and guarded personal-profile posting. Author identity is always resolved from the connected member — never hard-coded to a specific profile.
This listing is meant to be forked. After you fork, connect your LinkedIn account and run ./smoke-test on your copy. Do not treat the live @kody/linkedin integration as yours.
When To Use
- Verify the saved
linkedinOAuth integration and read the connected member’s identity or person URN - Call uncommon LinkedIn REST endpoints with
./request - Preview a text, article, image, or video post with
dryRun: true, then publish only afterconfirm: true - Upload article thumbnails through the Images API before publishing link-preview cards
- Delete a post by share or UGC post URN after explicit confirmation
- Run the native video upload flow: register, upload parts, finalize, then post
Do not use this package as an organization-page poster, a scraping client, or a place to paste access tokens.
OAuth setup
LinkedIn is bring-your-own OAuth. There is no built-in Kody LinkedIn app. Tokens come from the hosted connect page (createAuthenticatedFetch('linkedin')). Never paste a client secret or access token into chat.
- Open LinkedIn Developers → Create app.
- Auth → Authorized redirect URLs → add exactly
https://kody.codes/connect/oauth(the connect page shows the same value with a copy button). - Products → request Sign In with LinkedIn using OpenID Connect and Share on LinkedIn.
- Note the app’s Client ID and Client Secret (paste them into Kody, not into chat).
- Open this prefilled connect URL while signed in to Kody:
https://kody.codes/connect/oauth?provider=linkedinFirst-time setup also needs LinkedIn’s authorize and token URLs. Use this complete prefilled URL when the connection does not exist yet:
https://kody.codes/connect/oauth?provider=linkedin&authorizeUrl=https%3A%2F%2Fwww.linkedin.com%2Foauth%2Fv2%2Fauthorization&tokenUrl=https%3A%2F%2Fwww.linkedin.com%2Foauth%2Fv2%2FaccessToken&apiBaseUrl=https%3A%2F%2Fapi.linkedin.com&flow=confidential&tokenExchangeStyle=form&allowedHosts=api.linkedin.com%2Cwww.linkedin.com&dashboardUrl=https%3A%2F%2Fwww.linkedin.com%2Fdevelopers%2Fapps&scopes=openid%20profile%20email%20w_member_socialDecoded:
- Redirect / callback:
https://kody.codes/connect/oauth - Authorize:
https://www.linkedin.com/oauth/v2/authorization - Token:
https://www.linkedin.com/oauth/v2/accessToken - API base:
https://api.linkedin.com - Flow:
confidentialwithtokenExchangeStyle=form - Hosts:
api.linkedin.com,www.linkedin.com - Scopes:
openid,profile,email,w_member_social
- Paste the Client ID and Client Secret into the Kody form, continue to LinkedIn, and approve.
- Run Smoke test on the forked package.
Reconnect the same connection with https://kody.codes/connect/oauth?provider=linkedin. Kody reuses the saved authorize/token endpoints.
LinkedIn does not issue a refresh token unless the app has approved partner access. When the access token expires, reconnect — do not invent a refresh flow.
Scopes
| Scope | Product | Used by |
|---|---|---|
openid | Sign In with LinkedIn using OpenID Connect | ./smoke-test, ./get-user-info, ./get-person-urn |
profile | Sign In with LinkedIn using OpenID Connect | ./get-user-info (name / picture / locale flags) |
email | Sign In with LinkedIn using OpenID Connect | ./get-user-info (email presence flags only) |
w_member_social | Share on LinkedIn | posting, upload, and delete helpers |
./request uses whatever scopes the saved token already has. Identity helpers do not return names or emails from ./smoke-test.
Auth / transport
There is no usable public LinkedIn OpenAPI for the full REST surface. This package uses a compact request helper that authenticates with createAuthenticatedFetch('linkedin') and injects LinkedIn protocol headers (LinkedIn-Version, X-Restli-Protocol-Version) for Rest.li endpoints.
Posting helpers resolve author from /v2/userinfo (urn:li:person:{sub}) unless the caller passes an explicit urn:li:person:…. Do not hard-code a member URN in forks.
Smoke test
After connect or reconnect, invoke ./smoke-test in the forked package runtime (not a static import):
import { packages } from 'kody:runtime'
export default async function main() {
return await packages.invoke({
kodyId: 'linkedin',
exportName: './smoke-test',
})
}Success looks like { ok: true, integration: 'linkedin', hasSubject: true, personUrnPresent: true } and does not return names, emails, or the raw sub.
Low-level equivalent (execute exploration only):
import { createAuthenticatedFetch } from 'kody:runtime'
export default async function main() {
const linkedinFetch = await createAuthenticatedFetch('linkedin')
const response = await linkedinFetch('https://api.linkedin.com/v2/userinfo')
if (!response.ok) {
throw new Error('LinkedIn userinfo failed: ' + response.status)
}
const data = (await response.json()) as { sub?: string }
return { ok: true, hasSubject: Boolean(data.sub) }
}dryRun and posting
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.
import createTextPost from 'kody:@kody/linkedin/create-text-post'
export default async function main() {
return await createTextPost({ text: 'Draft text', 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.
Exports
kody:@kody/linkedin— package overview, connect URL, and scopeskody:@kody/linkedin/smoke-test— verify userinfo access without returning PIIkody:@kody/linkedin/get-user-info— LinkedIn OIDC userinfokody:@kody/linkedin/get-person-urn— authorpersonUrnfor postingkody:@kody/linkedin/request— authenticated LinkedIn API request helperkody:@kody/linkedin/create-post— generic Posts API create helper (mutating)kody:@kody/linkedin/create-text-post— text post helper (mutating)kody:@kody/linkedin/create-article-post— article link post helper (mutating)kody:@kody/linkedin/delete-post— delete a post by URN (mutating)kody:@kody/linkedin/register-image-upload— initialize image upload (mutating)kody:@kody/linkedin/create-image-post— image post helper (mutating)kody:@kody/linkedin/register-video-upload— initialize native video upload (mutating)kody:@kody/linkedin/upload-video-part— upload one video part (mutating)kody:@kody/linkedin/finalize-video-upload— finalize native video upload (mutating)kody:@kody/linkedin/create-video-post— video post helper (mutating)
Examples
import getPersonUrn from 'kody:@kody/linkedin/get-person-urn'
export default async function main() {
return await getPersonUrn()
}import request from 'kody:@kody/linkedin/request'
export default async function main() {
return await request({ path: '/v2/userinfo', dryRun: true })
}Troubleshooting
- Connect page asks for authorize/token URLs: use the complete first-time URL in OAuth setup.
- HTTP 401 / expired token: reconnect at
https://kody.codes/connect/oauth?provider=linkedin. LinkedIn access tokens do not refresh without partner access. - HTTP 403 on posting: the LinkedIn app needs the Share on LinkedIn product and
w_member_social. Reconnect after the product is approved. redirect_urimismatch: the LinkedIn app redirect must be exactlyhttps://kody.codes/connect/oauth.
Report this listing
Log in to report this listing.