mcpField
S
social-media-api-best-practices

social-media-api-best-practices

github.com/zernio-dev開発369 回インストール13 週間前に更新
リポジトリ

social-media-api-best-practices について

ソーシャルメディアへの投稿連携を開発する人向けのリファレンススキルです。掲載された各 API の認証、レート制限、メディアアップロード、エラー処理を扱います。

主な機能

  1. 01プラットフォーム別のOAuthとトークン処理
  2. 02レート制限とリトライの方法
  3. 03メディアのサイズと形式の要件
  4. 04分割・再開可能な動画アップロード
  5. 05プラットフォーム別のエラーと仕様差

活用シーン

  1. 01ソーシャル API の認証実装
  2. 02レート制限に沿った投稿キュー設計
  3. 03大容量動画の安定したアップロード

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zernio-dev/social-media-api-best-practices --skill social-media-api-best-practices

インストール後、Claude Code で/social-media-api-best-practicesを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

ソーシャルメディア API のベストプラクティス

13 のプラットフォームで 100 万件以上の投稿をスケジュールした実績に基づくパターンです。


1. 認証パターン

OAuth 2.0 プラットフォーム

Instagram(Meta Graph API)

  • 2 段階のトークン交換:短期間(1 時間)→ 長期間(60 日)
  • auth_type: 'rerequest' を使用して、権限の再要求を強制します
  • トークン交換エンドポイント:ig_exchange_token の grant type
  • スコープ:instagram_business_basic、instagram_business_content_publish、instagram_business_manage_messages、instagram_business_manage_comments、instagram_business_manage_insights

Twitter/X(PKCE 必須)

  • S256 コードチャレンジを使用した PKCE が必須です
  • state にコード検証子を埋め込みます:${state}-cv_${codeVerifier}
  • スコープ:tweet.read、tweet.write、users.read、offline.access、media.write、dm.read、dm.write
  • アクセストークン:2 時間、リフレッシュトークン:長期間有効
function generateCodeVerifier(): string {
  return crypto.randomBytes(32).toString('base64url');
}

function generateCodeChallenge(verifier: string): string {
  return crypto.createHash('sha256').update(verifier).digest('base64url');
}

function extractCodeVerifierFromState(state: string): string {
  const match = state.match(/-cv_(.+)$/);
  return match ? match[1] : '';
}

TikTok

  • API 監査の承認には、UX の完全な準拠が必要です
  • 表示必須項目:privacy_level セレクター、コメント/デュエット/スティッチの切り替え
  • 商用コンテンツの開示が必須です
  • スコープ:user.info.basic、user.info.profile、user.info.stats、video.publish、video.upload、video.list

LinkedIn

  • 常にヘッダーに X-RestLi-Protocol-Version: 2.0.0 を含めます
  • API バージョンヘッダー:LinkedIn-Version: 202511(一部のエンドポイントでは 202505 または 202401 を使用します)
  • アクセストークン:60 日、リフレッシュトークン:365 日
  • 個人(urn:li:person:)と組織(urn:li:organization:)の両方の投稿に対応します
  • 個人用スコープ:openid、profile、r_basicprofile、email、w_member_social、w_member_social_feed、r_member_postAnalytics、r_member_profileAnalytics、r_1st_connections_size
  • 組織用スコープ:w_organization_social、w_organization_social_feed、r_organization_admin、r_organization_social、r_organization_social_feed、r_organization_followers

YouTube

  • リフレッシュトークンを取得するには、access_type=offline と prompt=consent の両方が必要です
  • ユーザーチャンネルとブランドアカウントの両方に対応します
  • スコープ:youtube.upload、youtube、youtube.force-ssl、yt-analytics.readonly

Facebook

  • ページアクセストークンはユーザートークンとは別です
  • /me/accounts を介してユーザートークンをページトークンに交換します
  • scheduled_publish_time による予約投稿に対応します
  • スコープ:pages_manage_posts、pages_show_list、pages_read_engagement、pages_manage_engagement、pages_read_user_content、business_management、pages_messaging

Threads

  • Instagram(Meta)と同様に、2 段階のトークン交換を行います
  • th_exchange_token の grant type を使用します
  • スコープ:threads_basic、threads_content_publish、threads_manage_replies、threads_read_replies、threads_manage_insights、threads_delete

Pinterest

  • リフレッシュトークンを使用する OAuth 2.0
  • スコープ:pins:read、pins:write、boards:read、boards:write、user_accounts:read

Google Business Profile

  • スコープ business.manage、userinfo.profile、userinfo.email を使用する Google OAuth
  • My Business v4 API を使用します

Reddit

  • 長期間有効なトークンには duration=permanent を指定した OAuth を使用します
  • 厳格なユーザーエージェント要件 - Reddit は説明的なユーザーエージェントを必須としています
  • スコープ:identity、submit、read、mysubreddits、privatemessages、history、edit、vote
const headers = {
  'User-Agent': 'YourApp/1.0 by /u/YourUsername'
};
非 OAuth プラットフォーム

Bluesky(AT Protocol)

  • 従来型の OAuth は使用せず、DID(分散識別子)を使用します
  • AT Protocol の accessJwt と refreshJwt を使用して認証します
  • PDS(パーソナルデータサーバー)の解決が必要です
  • ExpiredToken エラー時に自動でリフレッシュします
// Bluesky authentication
const session = await agent.login({
  identifier: 'user.bsky.social',
  password: 'app-password'
});
// session contains: accessJwt, refreshJwt, did, handle

Snapchat

  • Basic 認証、許可リスト限定(Snapchat 開発チームの承認が必要です)
  • 公開 API は限定的で、プロフィール API が中心です
  • スコープ:snapchat-profile-api

Telegram

  • ボットトークンのみ - OAuth フローはありません
  • トークン形式:BOT_ID:SECRET
  • ユーザー認証はなく、ボットをチャット/チャンネルに追加する必要があります

2. レート制限の戦略

プラットフォーム固有の制限
プラットフォーム主な制限対策
Instagramアカウントあたり1日100件の投稿キューに入れて分散
TikTok24時間あたり保留中のアップロードは5件処理完了を待つ
Twitter3段階の制限(アプリ+ユーザー+エンドポイント)3つすべてを確認
LinkedIn一般利用は寛容、大量処理には厳格慎重にバッチ処理
YouTubeチャンネルごとの1日あたりのアップロード割り当て割り当てを監視
Reddit1分あたり60リクエストヘッダーに従う
Threads厳しいレート制限すぐに失敗する
Twitterの3段階のレート制限

Twitterには3段階の制限があります。すべて確認してください。

  1. アプリレベルの24時間制限(最も厳しい)
  2. ユーザーレベルの24時間制限
  3. エンドポイント固有のレート制限
function parseRateLimitHeaders(headers: Headers) {
  return {
    // Endpoint limits
    remaining: parseInt(headers.get('x-rate-limit-remaining') || '0'),
    reset: parseInt(headers.get('x-rate-limit-reset') || '0'),
    // App-level 24h limits
    appRemaining: parseInt(headers.get('x-app-limit-24hour-remaining') || '0'),
    appReset: parseInt(headers.get('x-app-limit-24hour-reset') || '0'),
    // User-level 24h limits
    userRemaining: parseInt(headers.get('x-user-limit-24hour-remaining') || '0'),
    userReset: parseInt(headers.get('x-user-limit-24hour-reset') || '0'),
  };
}
指数バックオフのパターン
async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 5000
): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (!isRetryableError(error) || attempt === maxRetries) throw error;
      const delay = Math.min(baseDelay * Math.pow(2, attempt), 30000);
      await sleep(delay);
    }
  }
  throw new Error('Max retries exceeded');
}

function isRetryableError(error: any): boolean {
  const status = error.status || error.statusCode;
  return [429, 500, 502, 503].includes(status);
}

3. メディア要件

プラットフォームごとの全制限
プラットフォーム画像の最大サイズ動画の最大サイズアスペクト比特記事項
Instagram8MBストーリーズは100MB、リールは300MBフィードは4:5~1.91:1、ストーリーズは9:16カルーセルは10件
TikTok20MB4GB、3秒~10分9:16固定写真カルーセルは35件
Twitter5MB512MB、2分20秒柔軟画像は1~4枚
LinkedIn8MB5GB柔軟画像カルーセルは20枚
YouTubeサムネイルは2MB256GB16:9を推奨再開可能なアップロード
Facebook10MB4GB柔軟複数画像は10枚
Threads8MB1GB、5分縦向きの9:16カルーセルは10件
Pinterest32MB2GB柔軟カバー画像が必要
Bluesky1MB50MB、3分柔軟AT Protocol
Snapchat20MB500MB9:16AES 暗号化
Google Business5MB該当なし柔軟画像のみ
Reddit20MB該当なし柔軟URL 経由
Telegram10MB50MB柔軟4096文字制限
大きなファイルはストリーミングするのが鉄則

ファイル全体をメモリに読み込まないでください。

// BAD - loads entire file into memory
const buffer = await fetch(url).then(r => r.arrayBuffer());

// GOOD - streams directly
const response = await fetch(url);
await uploadToPlatform(response.body); // Pass the stream
問題のあるメディアソース

以下のホストは、メディアへの直接アクセスの代わりにHTMLを返したり、タイムアウトしたりします。

const PROBLEMATIC_HOSTS = [
  'drive.google.com',
  'docs.google.com',
  'dropbox.com',
  'onedrive.live.com',
  '1drv.ms'
];

// Solution: Re-host to your own storage first
if (PROBLEMATIC_HOSTS.some(h => url.includes(h))) {
  url = await reHostToStorage(url);
}
Dropbox の URL 修正
function fixDropboxUrl(url: string): string {
  if (url.includes('dropbox.com') && url.includes('dl=0')) {
    return url.replace('dl=0', 'dl=1');
  }
  return url;
}

4. 動画アップロードのパターン

分割アップロード(Twitter)
async function chunkedUpload(videoBuffer: Buffer, mediaType: string) {
  const CHUNK_SIZE = 4 * 1024 * 1024; // 4MB chunks

  // INIT
  const initRes = await api.post('/media/upload', {
    command: 'INIT',
    total_bytes: videoBuffer.length,
    media_type: mediaType
  });
  const mediaId = initRes.media_id_string;

  // APPEND chunks
  for (let i = 0; i < Math.ceil(videoBuffer.length / CHUNK_SIZE); i++) {
    const chunk = videoBuffer.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE);
    await api.post('/media/upload', {
      command: 'APPEND',
      media_id: mediaId,
      media: chunk.toString('base64'),
      segment_index: i
    });
  }

  // FINALIZE
  await api.post('/media/upload', {
    command: 'FINALIZE',
    media_id: mediaId
  });

  // Poll for processing
  await pollProcessingStatus(mediaId);

  return mediaId;
}
再開可能なアップロード(YouTube)

YouTube は、大きなファイルを確実にアップロードできるよう、再開可能なアップロードをサポートしています。youtube-chunked-upload モジュールを使うか、再開可能なアップロードプロトコルを実装してください。

処理状況のポーリング
async function pollProcessingStatus(mediaId: string, maxWaitMs = 300000) {
  const startTime = Date.now();

  while (Date.now() - startTime < maxWaitMs) {
    const status = await api.get(`/media/upload?command=STATUS&media_id=${mediaId}`);

    if (status.processing_info?.state === 'succeeded') return;
    if (status.processing_info?.state === 'failed') {
      throw new Error(status.processing_info.error?.message || 'Processing failed');
    }

    const checkAfter = (status.processing_info?.check_after_secs || 5) * 1000;
    await sleep(checkAfter);
  }

  throw new Error('Processing timeout');
}

5. エラー処理

エラーの分類
type ErrorType = 'refresh-token' | 'retry' | 'user-error';

function categorizeError(platform: string, error: any): ErrorType {
  if (isTokenError(error)) return 'refresh-token';
  if (isTemporaryError(error)) return 'retry';
  return 'user-error';
}
Instagram のエラーコード
コード意味対処
2207001スパムを検出ユーザーエラー
2207003メディアのダウンロードがタイムアウト再試行
2207004画像が大きすぎる(>8MB)圧縮
2207006メディアが見つからないユーザーエラー
2207026サポートされていない動画形式再エンコード
22070421日あたりの投稿数が100件を超過24時間待つ
2207050ユーザーが制限されているユーザーエラー
2207051ブロックされた(ただし投稿済みの可能性あり!)確認
2207052メディアの取得に失敗直接 URL を使用
Instagram のエッジケース:2207051

Instagram のスパム対策により、「ブロックされた」と返されても、実際には投稿されている場合があります。

async function handleInstagram2207051(accountId: string) {
  await sleep(5000);

  const recentMedia = await getRecentMedia(accountId);
  const justPosted = recentMedia.find(m =>
    Date.now() - new Date(m.timestamp).getTime() < 60000
  );

  if (justPosted) {
    return { success: true, postId: justPosted.id };
  }
  throw new Error('Post actually failed');
}
TikTok のエラーコード
コード意味対処
access_token_invalidトークンの有効期限切れ更新
scope_not_authorized権限がない再接続
rate_limit_exceededリクエストが多すぎる再試行
spam_risk_*コンテンツがフラグ付けされたユーザーエラー
file_format_check_failed無効な形式ユーザーエラー
unaudited_client_can_only_post_to_private開発モード非公開のみ
Twitter/X のエラーコード
コード意味対処
invalid_grantトークンが取り消された再接続
usage-cappedレート制限中再試行
duplicate同じコンテンツが存在ユーザーエラー
186ツイートが長すぎるユーザーエラー
Bluesky のエラーコード
コード意味対処
ExpiredTokenJWT の有効期限切れ自動更新
InvalidToken無効なトークン再接続
XRPCNotSupportedアプリパスワードにDM権限がない完全な認証を使用
Reddit エラーコード
コード意味対処
invalid token有効期限切れ更新
not allowed to submitサブレディットの制限ユーザー側のエラー
RATELIMITリクエストが速すぎます間隔を空けて再試行
NO_LINKSリンクは許可されていませんユーザー側のエラー
Telegram エラーコード
コード意味対処
message too long4096 文字超切り詰め
chat not found無効なチャット IDユーザー側のエラー
bot was blockedユーザーがボットをブロックしましたユーザー側のエラー

6. プラットフォーム固有の注意点

JavaScript の大きな整数 ID(Instagram/Facebook)

Instagram の ID は MAX_SAFE_INTEGER(17 桁以上)を超えます:

function safeJsonParse(text: string) {
  // Wrap large integers in quotes before parsing
  const safe = text.replace(/"id"\s*:\s*(\d{15,})/g, '"id":"$1"');
  return JSON.parse(safe);
}
Twitter の文字数カウント
function countTwitterCharacters(text: string): number {
  let count = 0;

  // URLs always count as 23 characters
  const urlRegex = /https?:\/\/[^\s]+/g;
  const urls = text.match(urlRegex) || [];
  const textWithoutUrls = text.replace(urlRegex, '');

  count += urls.length * 23;

  // Emojis and CJK count as 2
  for (const char of textWithoutUrls) {
    count += char.match(/[\u{1F600}-\u{1F64F}]|[\u4e00-\u9fff]/u) ? 2 : 1;
  }

  return count;
}
Twitter のプラン別文字数制限
プラン制限
無料280
Premium4,000
Premium+25,000
LinkedIn のテキストエスケープ

予約文字:| { } [ ] ( ) < > # \ * _ ~

注:投稿に \@ が表示されるのを避けるため、@ は意図的にエスケープしていません。URN のメンション @[Name](urn:li:person:ID) とハッシュタグ #tag は自動的に保持されます。

function escapeLinkedInText(text: string): string {
  // Reserved chars (excluding @ to preserve readability)
  const reserved = /[\|\{\}\[\]\(\)\<\>\#\\\*\_\~]/;

  let output = '';
  let i = 0;

  while (i < text.length) {
    // Preserve URN mentions: @[Display Name](urn:li:person:ID)
    if (text[i] === '@' && text[i + 1] === '[') {
      const closeBracket = text.indexOf(']', i + 2);
      if (closeBracket !== -1 && text.substring(closeBracket + 1, closeBracket + 9) === '(urn:li:') {
        const closeParen = text.indexOf(')', closeBracket + 9);
        if (closeParen !== -1) {
          output += text.substring(i, closeParen + 1);
          i = closeParen + 1;
          continue;
        }
      }
    }

    // Preserve hashtags: #word
    if (text[i] === '#' && /[a-zA-Z0-9_]/.test(text[i + 1] || '')) {
      output += text[i++];
      while (i < text.length && /[a-zA-Z0-9_]/.test(text[i])) {
        output += text[i++];
      }
      continue;
    }

    // Escape reserved characters
    output += reserved.test(text[i]) ? '\\' + text[i] : text[i];
    i++;
  }

  return output;
}
TikTok の UX コンプライアンス要件

TikTok の API 監査では、UX 要件への完全な準拠が必要です:

  • ユーザーがプライバシーレベルを手動で選択する必要があります(デフォルト値は不可)
  • コメント/デュエット/スティッチの切り替え項目を表示する必要があります
  • ユーザーの確認を伴う商用コンテンツの開示
  • アップロード前のコンテンツプレビュー
  • 明示的な同意の宣言
Reddit の User-Agent 要件

Reddit は説明的なユーザーエージェントを厳格に要求します:

// BAD - will be blocked
headers['User-Agent'] = 'axios/1.0';

// GOOD - descriptive format
headers['User-Agent'] = 'MyApp/1.0 (by /u/developer_username)';
Telegram の HTML サブセット

次の HTML タグのみがサポートされています:

<b>, <strong>       
<i>, <em>           
<u>                 
<s>, <strike>, <del>
<code>              
<pre>               
<a href="...">      
<tg-spoiler>        
<blockquote>        

7. 特殊なプロトコル

AT Protocol(Bluesky)

Bluesky は OAuth ではなく、AT Protocol を使用します:

主な概念:

  • DID(分散識別子):ユーザーの永続的な ID(例:did:plc:abc123)
  • PDS(パーソナルデータサーバー):ユーザーデータが保存される場所
  • ハンドル:人が読める名前(例:user.bsky.social)

リッチテキストのファセット:

メンションとリンクでは、バイトオフセットによる位置指定を使用します:

function createFacets(text: string): Facet[] {
  const facets: Facet[] = [];
  const encoder = new TextEncoder();

  // Find mentions
  const mentionRegex = /@([a-zA-Z0-9.-]+)/g;
  let match;

  while ((match = mentionRegex.exec(text)) !== null) {
    const beforeText = text.slice(0, match.index);
    const byteStart = encoder.encode(beforeText).length;
    const byteEnd = byteStart + encoder.encode(match[0]).length;

    facets.push({
      index: { byteStart, byteEnd },
      features: [{
        $type: 'app.bsky.richtext.facet#mention',
        did: await resolveDid(match[1])
      }]
    });
  }

  return facets;
}
Snapchat の AES-256-CBC 暗号化

Snapchat では、アップロード前にメディアを暗号化する必要があります:

import crypto from 'crypto';

function encryptForSnapchat(buffer: Buffer): {
  encrypted: Buffer;
  key: string;
  iv: string;
} {
  const key = crypto.randomBytes(32); // 256 bits
  const iv = crypto.randomBytes(16);  // 128 bits

  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
  const encrypted = Buffer.concat([
    cipher.update(buffer),
    cipher.final()
  ]);

  return {
    encrypted,
    key: key.toString('base64'),
    iv: iv.toString('base64')
  };
}

8. 簡易デバッグチェックリスト

投稿に失敗した場合:

  1. トークンの有効性を確認 - アクセストークンの有効期限が切れていませんか?
  2. レート制限を確認 - 日次/時間単位の制限に達していませんか?
  3. メディア URL を確認 - プラットフォームから直接取得できますか?
  4. メディア仕様を確認 - 形式、サイズ、寸法、アスペクト比は適切ですか?
  5. 2207051 のケースを確認 - エラーが出ても投稿されていませんか?
  6. アカウントの状態を確認 - アカウントが制限されていませんか?
  7. サブレディットのルールを確認 - (Reddit)リンクは許可されていますか?フレアは必要ですか?
  8. ユーザーエージェントを確認 - (Reddit)十分に説明的ですか?
  9. ボットの権限を確認 - (Telegram)送信権限のあるチャットにボットが参加していますか?

9. 推奨アーキテクチャ

your-app/
├── lib/
│   ├── platforms/
│   │   ├── base.ts           # Abstract base class
│   │   ├── instagram.ts
│   │   ├── tiktok.ts
│   │   ├── twitter.ts
│   │   ├── bluesky.ts        # AT Protocol
│   │   ├── snapchat.ts       # With encryption
│   │   └── ...
│   ├── utils/
│   │   ├── rate-limiter.ts
│   │   ├── media-handler.ts
│   │   ├── error-mapper.ts
│   │   └── encryption.ts     # For Snapchat
│   └── queue/
│       └── scheduler.ts

10. プラットフォーム比較一覧

機能InstagramTikTokTwitterLinkedInYouTubeFacebookThreadsPinterestBlueskySnapchatGBPRedditTelegram
OAuth2 段階標準PKCE標準Google標準2 段階基本AT Proto基本Google標準ボットトークン
トークンの有効期間60 日2 年以上2 時間以上+更新更新は 365 日1 年60 日以上60 日該当なし自動該当なし1 年無期限該当なし
動画ありありありありありありありありありありなしなしあり
カルーセル1035420なし1010なし4なしなしギャラリーアルバム 10
スケジュール設定API下書きなしAPIAPIAPIAPIなしAPIなしなしなしなし
DMありなしありありなしありなしなしありありなしありあり
アナリティクスありありありありありありありありなしなし制限ありなし制限あり

このガイドについて

このガイドは、Lateが管理しており、13のプラットフォームすべてで100万件以上の投稿をスケジュールする中で得た実際のパターンをもとに作成されています。

ここに記載されているすべてのエラーコード、例外的なケース、特有の挙動は、実運用での経験に基づいています。Instagramの2207051の落とし穴は、何時間もデバッグした末に発見しました。Snapchatの暗号化要件は、苦労して学びました。

ソーシャルメディアアプリを開発していますか? これらの連携をすべて自分で実装・保守するのは避けたいという方には、LateのAPIが複雑な処理を担います。OAuth、メディア処理、レート制限、エラー処理、スケジュール設定を、1つの連携で13のプラットフォームすべてに対応できます。


Lateが管理 — 開発者向けソーシャルメディアスケジュール設定API

コメント