@kentcdodds/linkedin
Headless helpers for LinkedIn identity, requests, and guarded posting using the saved linkedin OAuth integration.
AGENTS.md
140 lines · 5.3 KB · Markdown@kentcdodds/linkedin — agent notes
Human setup and intent live in README.md. This file is for
agents: imports, smoke/dryRun snippets, and edge cases. Secrets / integration
ids by name only — never paste token values. Do not disable live webhooks
or jobs.
Auth / secrets
| Kind | Name | Notes |
|---|---|---|
| OAuth integration | linkedin | createAuthenticatedFetch('linkedin') |
| User secret | linkedinAccessToken | Managed by OAuth connect |
| User secret | linkedinClientSecret | Managed by OAuth connect |
API origin: https://api.linkedin.com. Uploads may use https://www.linkedin.com.
Reconnect: /connect/oauth?provider=linkedin. Posting needs w_member_social.
Import paths
| Export | Import |
|---|---|
| overview | kody:@kentcdodds/linkedin |
| smoke-test | kody:@kentcdodds/linkedin/smoke-test |
| get-user-info | kody:@kentcdodds/linkedin/get-user-info |
| get-person-urn | kody:@kentcdodds/linkedin/get-person-urn |
| request | kody:@kentcdodds/linkedin/request |
| create-post | kody:@kentcdodds/linkedin/create-post |
| create-text-post | kody:@kentcdodds/linkedin/create-text-post |
| create-article-post | kody:@kentcdodds/linkedin/create-article-post |
| delete-post | kody:@kentcdodds/linkedin/delete-post |
| register-image-upload | kody:@kentcdodds/linkedin/register-image-upload |
| create-image-post | kody:@kentcdodds/linkedin/create-image-post |
| register-video-upload | kody:@kentcdodds/linkedin/register-video-upload |
| upload-video-part | kody:@kentcdodds/linkedin/upload-video-part |
| finalize-video-upload | kody:@kentcdodds/linkedin/finalize-video-upload |
| create-video-post | kody:@kentcdodds/linkedin/create-video-post |
Prefer static kody:@kentcdodds/linkedin/... imports from execute.
Smoke test (read-only)
import smokeTest from 'kody:@kentcdodds/linkedin/smoke-test'
export default async function main() {
return await smokeTest()
// => { ok: true, subPreview: '...' }
}import getPersonUrn from 'kody:@kentcdodds/linkedin/get-person-urn'
export default async function main() {
return await getPersonUrn()
}dryRun / confirm (writes)
Posting and upload helpers require confirm: true unless dryRun: true:
import createTextPost from 'kody:@kentcdodds/linkedin/create-text-post'
export default async function main() {
return await createTextPost({ text: 'Draft text', dryRun: true })
// => { dryRun: true, body: { commentary: 'Draft text', ... } }
}import createArticlePost from 'kody:@kentcdodds/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,
})
}Same guard for create-post, delete-post, create-image-post,
create-video-post, and upload register/finalize helpers.
Inbound webhooks
Declared webhooks (do not disable):
| Webhook name | Export |
|---|---|
create-post | ./create-post |
delete-post | ./delete-post |
create-article-post | ./create-article-post |
register-video-upload | ./register-video-upload |
finalize-video-upload | ./finalize-video-upload |
create-video-post | ./create-video-post |
Inbound POSTs use inputMode: params. Mutating webhooks still honor
confirm / dryRun — a webhook hit is not auto-approval to publish. Minted
URLs are credentials; never paste them in chat.
Trusted clients (promo scheduler, BWK CLI) should prefer the single
@kentcdodds/social dispatch webhook with routes
linkedin/register-video-upload, linkedin/finalize-video-upload, and
linkedin/create-video-post rather than minting leaf URLs for every action.
Edge cases
- LinkedIn does not scrape Open Graph for API-created article posts. Pass
title,description, and either an existing imagethumbnailURN or HTTPSthumbnailUrl(helper uploads via Images API). - Video flow:
register-video-upload→upload-video-part(parts) →finalize-video-upload→create-video-post. requestinjectsLinkedIn-VersionandX-Restli-Protocol-Version; paths stay onapi.linkedin.com.- Smoke must not return full PII (userinfo is summarized).
- Always prefer dry-run before any live write; smoke is not permission to post.
Input schema (create-post / create-text-post / media)
./create-post and ./create-text-post parse with Remix Schema
(unknownKeys: 'error'). Commentary/text aliases: text, message, body
(and commentary on create-post). Unknown top-level keys are rejected with
did-you-mean hints. LinkedIn API content / distribution objects remain
allowed on create-post as controlled extension bags — do not invent
reply/parent aliases (LinkedIn reply threading is not modeled here).
./create-article-post, ./create-image-post, and ./create-video-post use the same
Remix Schema bar. Text aliases: commentary / message / body. Article URL
aliases: link / articleUrl / href. Thumbnail aliases: thumbnail_url /
imageUrl. Image URN aliases: image_urn / asset. Video: video_urn or
uploadFromUrl / upload_from_url / videoUrl / uploadFromBytes. Unknown
keys are rejected with did-you-mean hints.