Futuristic Imagination LLC 代表の佐藤琢也です。
「同じ手作業を二度繰り返さない」をモットーに、日々AIと自動化の仕組みを開発・運用しています。弊社では、Next.jsとGemini APIを駆使して9言語18サイトのAIオウンドメディアを一人で運用し、年間6,570記事を自動生成しています。今回は、この自動化のノウハウの一つとして、LinkedInへの自動投稿システムの構築について深掘りしていこうと思います。
皆さん、せっかく書いた技術記事やブログ記事、手動で各SNSに投稿していませんか?「なんかこれって自動化できないかな?」と思ったことはありませんか?私はあります。そして、それを実現しました。
この記事を読めば、LinkedInへの投稿作業で消耗している毎日から解放され、よりクリエイティブな仕事に集中できるようになるはずです。LGTM連打の準備はいいですか?
この記事でわかること
- LinkedIn APIでの投稿自動化の全体像
- OAuth2.0の認証フローとAccess Tokenの取得方法
- TypeScriptとNode.jsを使った具体的なコード例
- 長期運用を見据えたAccess Tokenのリフレッシュ戦略
- 実際に直面した課題と解決策
🛠 LinkedIn APIで自動投稿システムを構築する背景
そもそも、なぜLinkedInの自動投稿システムが必要だったのか。
弊社では、採用コンテンツ自動化SaaSやMediaForge AIといったB2Bサービスを展開しており、企業様への情報発信も重要な業務です。また、自身の技術的な知見や日々の開発ログをZennやQiita、そしてYouTube(Shorts動画も毎日配信中)で発信しています。
これらを「手動で各SNSに転載する」という作業は、正直「非生産的極まりない」と感じていました。そこで、自動投稿パイプラインの一部としてLinkedInをターゲットにしたわけです。
「ドッグフーディング(自社実証)」ファーストの考え方で、自分たちが本当に使うもの、効果を実感できるものだけをサービス化するのが弊社のポリシー。LinkedIn自動投稿も、まさにその一つです。
🚀 LinkedIn APIでの投稿自動化:全体像を理解する
LinkedIn APIを使って投稿を行うためには、OAuth2.0による認証が必要です。大まかな流れは以下の通りです。
- LinkedIn Developerでのアプリケーション登録
- 認可コードの取得 (ユーザーが許可を与えるフェーズ)
- Access Tokenの取得 (認可コードを使ってAPIにアクセスするためのトークンを取得)
- Access Tokenのリフレッシュ (Access Tokenには有効期限があるため、定期的に更新)
- APIを使ったコンテンツ投稿
今回は、Next.jsで構築した弊社の管理システムから、Node.jsのスクリプトを呼び出す形で実装しました。Vercel CronやGitHub Actionsと連携させれば、完全自動化も夢ではありません。
1. LinkedIn Developerでのアプリケーション登録
まずは、LinkedIn Developer Centerにアクセスし、アプリケーションを登録します。
必要な権限は以下の通りです。(最低限)
r_liteprofiler_emailaddressw_member_social
特にw_member_socialは投稿に必要な権限なので忘れずに設定しましょう。また、リダイレクトURL(OAuth 2.0 Redirect URLs)の設定も重要です。これは認可コードを受け取るためのエンドポイントになります。開発環境ではhttp://localhost:3000/api/linkedin/callbackのようなものを設定しても良いでしょう。
2. 認可コードの取得
ユーザーがアプリケーションにアクセスを許可するフェーズです。
以下のURLにユーザーをリダイレクトさせます。
https://www.linkedin.com/oauth/v2/authorization?
response_type=code
&client_id={YOUR_CLIENT_ID}
&redirect_uri={YOUR_REDIRECT_URI}
&state={YOUR_STATE}
&scope=r_liteprofile%20r_emailaddress%20w_member_social
-
response_type=code: 認可コードを要求します。 -
client_id: アプリケーション登録時に取得したクライアントID。 -
redirect_uri: アプリケーション登録時に設定したリダイレクトURL。 -
state: CSRF攻撃防止のための任意の文字列。コールバック時に検証します。 -
scope: 要求する権限。URLエンコードが必要です。
このURLにアクセスすると、ユーザーはLinkedInの認証画面に飛び、アプリケーションへのアクセス許可を求められます。許可すると、設定したredirect_uriにcodeとstateがクエリパラメータとして付与されてリダイレクトされます。
例えば、http://localhost:3000/api/linkedin/callback?code=AQVN...&state=YOUR_STATE のようになります。
3. Access Tokenの取得
リダイレクトされたcallbackエンドポイントで、先ほど取得したcodeを使ってAccess Tokenを取得します。これはサーバーサイドで行うべき処理です。
// pages/api/linkedin/callback.ts (Next.js API Routeの例)
import type { NextApiRequest, NextApiResponse } from 'next';
import axios from 'axios';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { code, state } = req.query;
// CSRF対策: stateの検証
// if (state !== 'YOUR_STATE_FROM_STEP_2') {
// return res.status(403).json({ error: 'CSRF attack detected!' });
// }
if (!code) {
return res.status(400).json({ error: 'Authorization code not found.' });
}
const clientId = process.env.LINKEDIN_CLIENT_ID;
const clientSecret = process.env.LINKEDIN_CLIENT_SECRET;
const redirectUri = process.env.LINKEDIN_REDIRECT_URI;
if (!clientId || !clientSecret || !redirectUri) {
console.error('Environment variables for LinkedIn are not set.');
return res.status(500).json({ error: 'Server configuration error.' });
}
try {
const tokenResponse = await axios.post(
'https://www.linkedin.com/oauth/v2/accessToken',
new URLSearchParams({
grant_type: 'authorization_code',
code: code as string,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret,
}).toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);
const { access_token, expires_in } = tokenResponse.data;
// ここでaccess_tokenを安全に保存します(データベースなど)
console.log('Access Token:', access_token);
console.log('Expires in:', expires_in, 'seconds');
// 成功ページへのリダイレクトなど
res.redirect('/linkedin/success');
} catch (error) {
console.error('Error exchanging code for token:', (error as any).response?.data || error);
res.status(500).json({ error: 'Failed to get access token.' });
}
}
これで、access_tokenとexpires_in(有効期限)が取得できました。access_tokenは、APIリクエストの際にAuthorization: Bearer {access_token}ヘッダーに含めて使用します。このトークンは安全な場所に保存しましょう。データベースや環境変数、もしくは暗号化してファイル保存などが考えられます。
4. Access Tokenのリフレッシュ戦略
LinkedInのAccess Tokenは通常、数時間の有効期限があります。毎回ユーザーに認証を促すのは現実的ではありません。LinkedIn APIにはリフレッシュトークンがないため、残念ながらAccess Tokenが期限切れになった場合は、再度ユーザーに認証フローを踏んでもらう必要があります。
「え、じゃあ毎回ユーザーが認証するの?」と私も思いました。
しかし、「極限の少数精鋭・完全自動化モデル」を目指す私にとって、この手作業は受け入れがたい。
そこで、私は以下の戦略を取りました。
- 長期的なAccess Tokenの取得: LinkedInのAccess Tokenは通常1〜2時間の有効期限ですが、実はアプリケーションの登録時に**「Request access to share on LinkedIn on behalf of members without their continuous presence」**というオプションを申請することで、60日間有効なAccess Tokenを取得できます。これを申請して承認されるのが、運用の肝になります。
- 定期的な手動更新、もしくは通知: 60日間であれば、月1回程度の頻度で手動でAccess Tokenを更新することも、それほど大きな負担ではありません。または、Access Tokenの有効期限が近づいたら、Discordなどのチャットツールに自動で通知する仕組みを構築しておけば、更新忘れを防ぐことができます。
今回は、60日間のAccess Tokenを前提に、その後の投稿処理を進めます。
5. APIを使ったコンテンツ投稿
Access Tokenが手に入れば、あとはAPIを叩くだけです。
LinkedIn APIで投稿を行うエンドポイントはいくつかありますが、今回はシンプルなテキスト投稿とURL共有を例に取ります。
LinkedIn APIのバージョンは定期的に更新されるため、最新のドキュメントを確認することを強くお勧めします。
テキストとURLを投稿する例
// linkedinPoster.ts (投稿ロジックをまとめたファイル)
import axios from 'axios';
interface LinkedInPostOptions {
accessToken: string;
authorUrn: string; // 例: 'urn:li:person:YOUR_PROFILE_ID' または 'urn:li:organization:YOUR_ORGANIZATION_ID'
text: string;
url?: string; // 共有したいURL (オプション)
}
export async function postToLinkedIn(options: LinkedInPostOptions): Promise<any> {
const { accessToken, authorUrn, text, url } = options;
const postData: any = {
author: authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: text,
},
shareMediaCategory: 'NONE', // リンクなしの場合
},
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC',
},
};
if (url) {
postData.specificContent['com.linkedin.ugc.ShareContent'].shareMediaCategory = 'ARTICLE';
postData.specificContent['com.linkedin.ugc.ShareContent'].media = [
{
status: 'READY',
originalUrl: url,
},
];
}
try {
const response = await axios.post(
'https://api.linkedin.com/v2/ugcPosts', // UGC Posts API を利用
postData,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-Restli-Protocol-Version': '2.0.0', // APIバージョンを指定
},
}
);
console.log('LinkedIn post successful:', response.data);
return response.data;
} catch (error) {
console.error('Error posting to LinkedIn:', (error as any).response?.data || error);
throw new Error('Failed to post to LinkedIn.');
}
}
// 使用例
async function exampleUsage() {
const LINKEDIN_ACCESS_TOKEN = process.env.LINKEDIN_ACCESS_TOKEN || '';
// 自分のプロフィールIDまたは組織IDを取得する方法は、LinkedIn APIのドキュメントを参照するか、
// r_liteprofileスコープで /v2/me を叩いて取得できます。
const AUTHOR_URN = 'urn:li:person:YOUR_PROFILE_ID'; // 例
const postText = `AI自動化システムの開発奮闘記!Qiitaに新しい記事を投稿しました。\n\n「LinkedIn OAuth2.0で自動投稿システムを作る:Access Tokenの取得から投稿まで 〜自動化で”疲弊”から解放されるための最短ルート〜」\n\n手作業に消耗する日々はもう終わり!このシステムで、あなたも自動化の”沼”にハマりましょう。\n\n#AI #自動化 #Nextjs #TypeScript #LinkedInAPI`;
const postUrl = 'https://qiita.com/your_account/items/xxxxxxxxxxxxxxxxxxxx'; // Qiita記事のURL
try {
await postToLinkedIn({
accessToken: LINKEDIN_ACCESS_TOKEN,
authorUrn: AUTHOR_URN,
text: postText,
url: postUrl,
});
console.log('LinkedIn投稿が完了しました!');
} catch (error) {
console.error('LinkedIn投稿に失敗しました。', error);
}
}
// exampleUsage(); // 実際に実行する際はコメントアウトを外す
実際の運用と工夫
私自身、このシステムをNext.jsとVercel Cronを組み合わせたパイプラインに組み込んでいます。
具体的には、
- 新しい記事が生成されると(またはQiitaに投稿されると)Webhooksでトリガー
- 投稿内容(タイトル、URL、ハッシュタグなど)を生成AIで最適化
- 事前に取得・保存しておいたAccess Tokenを使用してLinkedIn APIをコール
- 投稿完了後、成功・失敗をDiscordに自動通知
といった流れで完全に自動化しています。これにより、年間6,570記事という膨大な量のコンテンツを、私一人で(AIエージェントを部下として並行稼働させながら)運用できています。
💎 直面した課題と解決策
課題1: Access Tokenのリフレッシュトークンがない問題
前述の通り、LinkedIn APIにはリフレッシュトークンがなく、Access Tokenは最大60日で失効します。これは自動運用において大きな課題でした。
解決策:
「Request access to share on LinkedIn on behalf of members without their continuous presence」を申請し、60日間の有効期限を持つAccess Tokenを取得しました。これにより、月1回程度のメンテナンスで済むようになり、実質的に半自動化を実現しています。また、有効期限の管理を自動化し、期限切れが近づいたらSlackやDiscordに通知するシステムも構築しました。
課題2: ugcPosts APIとshares APIの違い
過去のLinkedIn APIドキュメントにはshares APIというものもありましたが、現在はugcPosts API(UGC Posts API)が推奨されています。APIのバージョン管理と適切なエンドポイントの選択が重要でした。
解決策:
常に最新のLinkedIn APIドキュメントを確認し、推奨されているugcPosts APIを使用しました。また、X-Restli-Protocol-Versionヘッダーの指定も忘れずに行うことで、APIのバージョンに合わせたリクエストを送信しています。
課題3: 投稿内容の最適化とハッシュタグ
ただURLを貼るだけではエンゲージメントが伸びにくいという問題がありました。
解決策:
Gemini APIと連携させ、記事の内容を分析し、LinkedInのオーディエンスに響くようなキャッチーな紹介文と、関連性の高いハッシュタグを自動生成するようにしました。これにより、投稿の質が向上し、見込み客へのリーチが拡大しています。
「なんかこれってAIで最適化できないかな?」という疑問から、AIエージェントを部下のように使って解決できた良い例です。
🎯 まとめ
今回は、LinkedInのOAuth2.0認証からAccess Tokenの取得、そしてUGC Posts APIを使った自動投稿システムの実装について解説しました。
重要なポイントは以下の通りです。
- LinkedIn Developerで適切な権限を持つアプリケーションを登録する。
- OAuth2.0の認可コードフローを理解し、Access Tokenを取得する。
- 60日間有効なAccess Tokenを申請し、リフレッシュ問題に対処する。
- 最新のLinkedIn APIドキュメントに基づき、
ugcPostsAPIで投稿する。 - AIを活用して投稿内容を最適化し、エンゲージメントを高める。
手作業に消耗する日々はもう終わりにしませんか?「自分でエラーか確認してほしい」という言葉は、一次情報にあたり、問題を根本から解決する私の思考の傾向ですが、日々のルーティンワークも「まずは自動化できないか」と考えることで、無限の可能性が広がります。
Futuristic Imagination LLCでは、今回ご紹介したようなSNS自動投稿システムをはじめ、AIを活用したWordPressからNext.jsへの移行、SEO改善パイプラインの構築など、企業のDXを推進する受託開発を行っています。
「圧倒的な開発・検証スピード」と「徹底した自動化・省力化(仕組み化)」を武器に、お客様のビジネス成長をサポートします。
弊社の提供サービスにご興味がありましたら、ぜひ以下からお問い合わせください。
➡️ [https://www.futuristicimagination.co.jp/service/](https://www.futuristicimagi nation.co.jp/service/)
また、私の日々の開発ログやキャリアに関する知見はYouTubeでも発信しています。
転職・副業・キャリアに関するShorts動画を毎日配信中!
➡️ https://www.youtube.com/channel/UCFobIbWz1KDKaIdDqXpTPAA
ぜひチャンネル登録・高評価をお願いします!
皆さんのLGTMが、私の次の自動化へのモチベーションになります!
ありがとうございました。