Skip to content
← Public packages

@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

KindNameNotes
OAuth integrationlinkedincreateAuthenticatedFetch('linkedin')
User secretlinkedinAccessTokenManaged by OAuth connect
User secretlinkedinClientSecretManaged 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

ExportImport
overviewkody:@kentcdodds/linkedin
smoke-testkody:@kentcdodds/linkedin/smoke-test
get-user-infokody:@kentcdodds/linkedin/get-user-info
get-person-urnkody:@kentcdodds/linkedin/get-person-urn
requestkody:@kentcdodds/linkedin/request
create-postkody:@kentcdodds/linkedin/create-post
create-text-postkody:@kentcdodds/linkedin/create-text-post
create-article-postkody:@kentcdodds/linkedin/create-article-post
delete-postkody:@kentcdodds/linkedin/delete-post
register-image-uploadkody:@kentcdodds/linkedin/register-image-upload
create-image-postkody:@kentcdodds/linkedin/create-image-post
register-video-uploadkody:@kentcdodds/linkedin/register-video-upload
upload-video-partkody:@kentcdodds/linkedin/upload-video-part
finalize-video-uploadkody:@kentcdodds/linkedin/finalize-video-upload
create-video-postkody:@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 nameExport
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 image thumbnail URN or HTTPS thumbnailUrl (helper uploads via Images API).
  • Video flow: register-video-upload → upload-video-part (parts) → finalize-video-upload → create-video-post.
  • request injects LinkedIn-Version and X-Restli-Protocol-Version; paths stay on api.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.