要点
LinkedIn APIは、開発者がLinkedInのプロフェッショナルネットワークとプログラムで統合するための標準的な方法を提供します。OAuth 2.0認証、プロファイル、投稿、コメント、企業ページ、広告のためのRESTfulおよびGraphQLエンドポイントが用意されており、アプリごとに1日100〜500リクエストのレート制限があります。本記事では、認証設定から本番環境への統合までの実装手順を解説します。
はじめに
LinkedInは200以上の国と地域に9億人以上のユーザーが存在します。B2Bアプリやマーケティングツール、採用サービスを開発する際、LinkedIn APIの活用は欠かせません。
実務では、B2Bマーケターが手動でプレゼンスを管理すると、週15~20時間もかかることがあります。API統合を活用することで、コンテンツ配信、リード獲得、エンゲージメント分析、採用ワークフローの自動化が実現できます。
このガイドで、OAuth 2.0による認証、プロファイル取得、投稿、企業ページ管理、広告API、Webhook、デプロイ戦略まで、実装に必要な具体的手順を解説します。
💡 ApidogはAPI統合テストを効率化します。LinkedInエンドポイントのテスト、OAuthフローの検証、API応答の確認、権限エラーのデバッグまで1ワークスペースで完結。API仕様のインポートやモックレスポンス生成もサポート、チームでテストシナリオを共有可能です。
LinkedIn APIとは?
LinkedInはプロフェッショナルネットワークデータへのRESTful・GraphQL APIを提供しています。主な用途は以下です。
- ユーザープロファイル情報取得
- 企業ページと更新情報の管理
- 投稿、コメント、リアクションの操作
- つながり(限定的)
- 求人情報・応募管理
- LinkedIn広告管理
- リードジェンフォーム
- (一部パートナー向け)メッセージング
主な機能
| 機能 | 説明 |
|---|---|
| RESTful + GraphQL | 複数のAPIスタイル |
| OAuth 2.0 | ユーザー認証が必要 |
| レート制限 | アプリごとに1日あたり100〜500リクエスト |
| 企業ページ | 完全なCRUD操作 |
| 広告API | キャンペーン管理 |
| Webhook | リアルタイム通知 |
| メディアアップロード | 画像と動画 |
API製品
| API | アクセスレベル | ユースケース |
|---|---|---|
| LinkedInでサインイン | オープン | ユーザー認証 |
| プロファイルAPI | パートナー | ユーザープロファイルの読み取り |
| 企業管理者API | パートナー | 企業ページの管理 |
| 広告API | パートナー | 広告キャンペーン管理 |
| 求人投稿API | パートナー | 求人の投稿と管理 |
| マーケティング開発者プラットフォーム | パートナー | 完全なAPIアクセス |
APIバージョン
| バージョン | ステータス | 終了日 |
|---|---|---|
| v2 | 現行 | アクティブ |
| v1 | 廃止 | 2023年12月 |
開始方法:認証設定
ステップ1:LinkedIn開発者アカウントの作成
- LinkedIn開発者ポータルにアクセス
- LinkedInアカウントでサインイン
- 「マイアプリ」→「Create App」をクリック
- アプリの詳細(名前・ロゴ・説明)を入力
ステップ2:アプリ設定の構成
const LINKEDIN_CLIENT_ID = process.env.LINKEDIN_CLIENT_ID;
const LINKEDIN_CLIENT_SECRET = process.env.LINKEDIN_CLIENT_SECRET;
const LINKEDIN_REDIRECT_URI = process.env.LINKEDIN_REDIRECT_URI;
// これらはアプリのダッシュボードから取得します
// https://www.linkedin.com/developers/apps/{appId}/auth
ステップ3:必要な権限の要求
権限ごとに、どのデータへアクセスできるかが決まります。
| 権限 | 説明 | 承認が必要 |
|---|---|---|
r_liteprofile |
基本プロファイル(名前、写真) | 自動承認 |
r_emailaddress |
メールアドレス | 自動承認 |
w_member_social |
ユーザーの代わりに投稿 | パートナー認証 |
r_basicprofile |
完全なプロファイル | パートナー認証 |
r_organization_social |
企業ページアクセス | パートナー認証 |
w_organization_social |
企業ページへの投稿 | パートナー認証 |
rw_ads |
広告管理 | パートナー認証 |
r_ads_reporting |
広告分析 | パートナー認証 |
ステップ4:認証URLの構築
const getAuthUrl = (state, scopes = ['r_liteprofile', 'r_emailaddress']) => {
const params = new URLSearchParams({
response_type: 'code',
client_id: LINKEDIN_CLIENT_ID,
redirect_uri: LINKEDIN_REDIRECT_URI,
scope: scopes.join(' '),
state: state
});
return `https://www.linkedin.com/oauth/v2/authorization?${params.toString()}`;
};
// 使用例
const state = require('crypto').randomBytes(16).toString('hex');
const authUrl = getAuthUrl(state, ['r_liteprofile', 'r_emailaddress', 'w_member_social']);
console.log(`ユーザーを以下にリダイレクトします: ${authUrl}`);
ステップ5:コードをアクセストークンと交換する
const exchangeCodeForToken = async (code) => {
const response = await fetch('https://www.linkedin.com/oauth/v2/accessToken', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: LINKEDIN_CLIENT_ID,
client_secret: LINKEDIN_CLIENT_SECRET,
redirect_uri: LINKEDIN_REDIRECT_URI
})
});
const data = await response.json();
return {
accessToken: data.access_token,
expiresIn: data.expires_in // 60日
};
};
// コールバックを処理
app.get('/oauth/callback', async (req, res) => {
const { code, state } = req.query;
try {
const tokens = await exchangeCodeForToken(code);
// トークンを安全に保存
await db.users.update(req.session.userId, {
linkedin_access_token: tokens.accessToken,
linkedin_token_expires: Date.now() + (tokens.expiresIn * 1000)
});
res.redirect('/success');
} catch (error) {
console.error('OAuthエラー:', error);
res.status(500).send('認証に失敗しました');
}
});
ステップ6:アクセストークンの更新
const refreshAccessToken = async (refreshToken) => {
// 注:LinkedInはリフレッシュトークンを提供していません
// ユーザーは60日後に再認証が必要
// 有効期限通知を実装しましょう
};
// API呼び出し前にトークンの有効期限を確認
const ensureValidToken = async (userId) => {
const user = await db.users.findById(userId);
if (user.linkedin_token_expires < Date.now() + 86400000) { // 24時間
// ユーザーに再認証を通知
await notifyUserToReauth(user.id);
throw new Error('トークンの有効期限が切れました。再認証してください');
}
return user.linkedin_access_token;
};
ステップ7:認証済みAPI呼び出しの実行
const LINKEDIN_BASE_URL = 'https://api.linkedin.com/v2';
const linkedinRequest = async (endpoint, options = {}) => {
const accessToken = await ensureValidToken(options.userId);
const response = await fetch(`${LINKEDIN_BASE_URL}${endpoint}`, {
...options,
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'X-Restli-Protocol-Version': '2.0.0',
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`LinkedIn APIエラー: ${error.message}`);
}
return response.json();
};
// 使用例
const profile = await linkedinRequest('/me');
console.log(`こんにちは、${profile.localizedFirstName} ${profile.localizedLastName}さん`);
プロファイルアクセス
ユーザープロファイルの取得
const getUserProfile = async () => {
const response = await linkedinRequest('/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))');
return response;
};
// 使用例
const profile = await getUserProfile();
console.log(`名前: ${profile.localizedFirstName} ${profile.localizedLastName}`);
console.log(`ID: ${profile.id}`);
console.log(`写真: ${profile.profilePicture?.['displayImage~']?.elements?.[0]?.identifiers?.[0]?.identifier}`);
メールアドレスの取得
const getUserEmail = async () => {
const response = await linkedinRequest('/emailAddress?q=members&projection=(emailAddress*)');
return response;
};
// 使用例
const email = await getUserEmail();
console.log(`メール: ${email.elements?.[0]?.emailAddress}`);
利用可能なプロファイルフィールド
| フィールド | 権限 | 説明 |
|---|---|---|
id |
r_liteprofile | LinkedInメンバーID |
firstName |
r_liteprofile | 名 |
lastName |
r_liteprofile | 姓 |
profilePicture |
r_liteprofile | プロファイル写真URL |
headline |
r_basicprofile | プロフェッショナルヘッドライン |
summary |
r_basicprofile | 「概要」セクション |
positions |
r_basicprofile | 職歴 |
educations |
r_basicprofile | 学歴 |
emailAddress |
r_emailaddress | 主要メールアドレス |
コンテンツの投稿
投稿の作成
const createPost = async (authorUrn, postContent) => {
const response = await linkedinRequest('/ugcPosts', {
method: 'POST',
body: JSON.stringify({
author: authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: postContent.text
},
shareMediaCategory: 'NONE'
}
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
}
})
});
return response;
};
// 使用例
const post = await createPost('urn:li:person:ABC123', {
text: '新製品の発売を発表できることを嬉しく思います!🚀 #イノベーション #スタートアップ'
});
console.log(`投稿が作成されました: ${post.id}`);
画像を伴う投稿の作成
const createPostWithImage = async (authorUrn, postData) => {
// ステップ1:メディアアップロードを登録
const uploadRegistration = await linkedinRequest('/assets?action=registerUpload', {
method: 'POST',
body: JSON.stringify({
registerUploadRequest: {
recipes: ['urn:li:digitalmediaRecipe:feedshare-image'],
owner: authorUrn,
serviceRelationships: [
{
relationshipType: 'OWNER',
identifier: 'urn:li:userGeneratedContent'
}
]
}
})
});
const uploadUrl = uploadRegistration.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl;
const assetUrn = uploadRegistration.value.asset;
// ステップ2:提供されたURLに画像をアップロード
await fetch(uploadUrl, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + await getAccessToken(),
'Content-Type': 'application/octet-stream'
},
body: postData.imageBuffer
});
// ステップ3:アップロードした画像で投稿作成
const post = await linkedinRequest('/ugcPosts', {
method: 'POST',
body: JSON.stringify({
author: authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: postData.text
},
shareMediaCategory: 'IMAGE',
media: [
{
status: 'READY',
description: {
text: postData.imageDescription || ''
},
media: assetUrn,
title: {
text: postData.title || ''
}
}
]
}
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
}
})
});
return post;
};
動画を伴う投稿の作成
const createPostWithVideo = async (authorUrn, postData) => {
// 動画アップロードを登録
const uploadRegistration = await linkedinRequest('/assets?action=registerUpload', {
method: 'POST',
body: JSON.stringify({
registerUploadRequest: {
recipes: ['urn:li:digitalmediaRecipe:feedshare-video'],
owner: authorUrn,
serviceRelationships: [
{
relationshipType: 'OWNER',
identifier: 'urn:li:userGeneratedContent'
}
]
}
})
});
const assetUrn = uploadRegistration.value.asset;
// 動画をアップロード(アップロードURLを利用)
// ... アップロードロジック ...
// 投稿作成
const post = await linkedinRequest('/ugcPosts', {
method: 'POST',
body: JSON.stringify({
author: authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: { text: postData.text },
shareMediaCategory: 'VIDEO',
media: [{ status: 'READY', media: assetUrn }]
}
},
visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' }
})
});
return post;
};
メディアの仕様
| メディアタイプ | 形式 | 最大サイズ | 期間 |
|---|---|---|---|
| 画像 | JPG、PNG、GIF | 8MB | 該当なし |
| 動画 | MP4、MOV | 5GB | 最大15分 |
| ドキュメント | PDF、PPT、DOC | 100MB | 該当なし |
企業ページ管理
企業情報の取得
const getCompanyInfo = async (companyId) => {
const response = await linkedinRequest(
`/organizations/${companyId}?projection=(id,localizedName,vanityName,tagline,description,universalName,logoV2(original~:playableStreams),companyType,companyPageUrl,confirmedLocations,industries,followerCount,staffCountRange,website, specialties)`
);
return response;
};
// 使用例
const company = await getCompanyInfo('1234567');
console.log(`会社: ${company.localizedName}`);
console.log(`フォロワー: ${company.followerCount}`);
console.log(`ウェブサイト: ${company.website}`);
企業ページへの投稿
const createCompanyPost = async (organizationUrn, postContent) => {
const response = await linkedinRequest('/ugcPosts', {
method: 'POST',
body: JSON.stringify({
author: organizationUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: postContent.text
},
shareMediaCategory: postContent.media ? 'IMAGE' : 'NONE',
media: postContent.media ? [
{
status: 'READY',
media: postContent.media.assetUrn
}
] : []
}
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
}
})
});
return response;
};
// 使用例
const post = await createCompanyPost('urn:li:organization:1234567', {
text: '採用募集中!成長中のチームに参加しませんか。#キャリア #採用'
});
企業のフォロワーの取得
const getFollowerCount = async (organizationId) => {
const response = await linkedinRequest(
`/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:${organizationId}`
);
return response;
};
レート制限
レート制限の理解
| API | 制限 | 期間 |
|---|---|---|
| プロファイルAPI | 100リクエスト | 1日あたり |
| UGC投稿 | 50投稿 | 1日あたり |
| 企業管理者 | 500リクエスト | 1日あたり |
| 広告API | 100リクエスト | 1分あたり |
レート制限ヘッダー
| ヘッダー | 説明 |
|---|---|
X-Restli-Quota-Remaining |
残りのリクエスト数 |
X-Restli-Quota-Reset |
リセットまでの秒数 |
一般的な問題のトラブルシューティング
問題:401 Unauthorized(未承認)
対策:
- アクセストークンの有効期限(60日)を確認
- トークンスコープに必要なリソースがあるか確認
-
Authorization: Bearer {token}ヘッダーの有無を確認
問題:403 Forbidden(禁止)
対策:
- アプリが必要な権限を持つかチェック
- ユーザーが必要なスコープを承認済みか確認
- パートナー認証の有無を確認
問題:429 Rate Limited(レート制限)
対策:
- リクエストキューイングを実装
- レスポンスキャッシュで呼び出し頻度を抑える
- ポーリングの代わりにWebhookを利用
本番環境デプロイチェックリスト
本番リリース前に以下を確認しましょう。
- [ ] LinkedInパートナー認証を完了
- [ ] OAuth 2.0の安全なトークンストレージ実装
- [ ] トークン有効期限通知(60日)を設定
- [ ] レート制限とキューイングの導入
- [ ] Webhookエンドポイントを設定
- [ ] エラー処理を網羅的に実装
- [ ] すべてのAPI呼び出しのログ記録
- [ ] ブランドガイドラインの遵守レビュー
実際のユースケース
採用プラットフォーム
- 課題:複数チャネルへの手動投稿
- 解決策:LinkedIn Jobs API統合
- 結果:80%の時間削減・応募数3倍
B2Bマーケティング自動化
- 課題:一貫性のない投稿スケジュール
- 解決策:UGC APIによる自動投稿
- 結果:エンゲージメント5倍・ブランド統一
まとめ
LinkedIn APIはプロフェッショナルネットワーク機能への包括的アクセスを実現します。実装時のポイントは:
- OAuth 2.0認証(トークンは60日有効)
- プロファイル・投稿・企業ページAPIの活用
- レート制限(1日100〜500リクエスト)の管理
- ほとんどのAPIでパートナー認証が必要
- ApidogによるAPIテスト・チームコラボの効率化
よくある質問
LinkedIn APIにアクセスするにはどうすればよいですか?
LinkedIn開発者アカウントを作成し、アプリを登録。高度なアクセスにはパートナー認証を申請。
LinkedInに自動的に投稿できますか?
はい。個人投稿はw_member_social、企業投稿はw_organization_social権限でUGC APIを利用可能です。
LinkedInのレート制限とは何ですか?
APIごとに1日100〜500リクエスト(広告APIは1分100リクエスト)に制限されます。
LinkedInトークンはどのくらい持続しますか?
アクセストークンの有効期限は60日。継続利用には再認証が必要です。
ユーザーのつながりにアクセスできますか?
いいえ。プライバシーポリシー強化により、ほとんどのアプリはつながりAPIにアクセスできません。
Top comments (0)