关于 social-media-api-best-practices
面向开发社交媒体发布集成的参考技能,涵盖指南所列 API 的平台认证、速率限制、媒体上传与错误处理。
主要功能
- 01平台专属 OAuth 与令牌处理
- 02速率限制策略与重试模式
- 03媒体大小和格式要求
- 04分块及可恢复的视频上传
- 05平台错误处理与特殊规则
适用场景
- 01实现社交 API 认证
- 02按速率限制规划发布队列
- 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)
- 两步令牌交换:短期(1 小时)→ 长期(60 天)
- 使用
auth_type: 'rerequest'强制重新请求权限 - 交换端点:
ig_exchange_token授权类型 - 权限范围:
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
- 始终包含标头:
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
- 公共主页访问令牌与用户令牌分开
- 通过
/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)类似,采用两步令牌交换
- 使用
th_exchange_token授权类型 - 权限范围:
threads_basic、threads_content_publish、threads_manage_replies、threads_read_replies、threads_manage_insights、threads_delete
- 使用带刷新令牌的 OAuth 2.0
- 权限范围:
pins:read、pins:write、boards:read、boards:write、user_accounts:read
Google Business Profile
- Google OAuth 权限范围:
business.manage、userinfo.profile、userinfo.email - 使用 My Business v4 API
- 使用
duration=permanent获取长期令牌 - 严格的 user-agent 要求——Reddit 强制要求使用描述性 user-agent
- 权限范围:
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
- 基本身份验证,仅限白名单(需要 Snapchat 开发团队批准)
- 公共 API 有限,侧重个人资料 API
- 权限范围:
snapchat-profile-api
Telegram
- 仅支持机器人令牌——无 OAuth 流程
- 令牌格式:
BOT_ID:SECRET - 不进行用户身份验证,机器人必须添加到聊天或频道
2. 速率限制策略
平台特定限制
| 平台 | 主要限制 | 策略 |
|---|---|---|
| 每个账号每天 100 条帖子 | 排队并分散发布 | |
| TikTok | 每 24 小时最多 5 个待处理上传 | 等待处理完成 |
| 三级限制(应用 + 用户 + 端点) | 检查这三级限制 | |
| 一般限制宽松,批量操作限制严格 | 谨慎分批处理 | |
| YouTube | 每个频道有每日上传配额 | 监控配额 |
| 每分钟 60 个请求 | 遵守响应头指示 | |
| Threads | 限流严格 | 快速失败 |
Twitter 的三级速率限制
Twitter 有三级限制,请全部检查:
- 应用级 24 小时限制(限制最严格)
- 用户级 24 小时限制
- 端点特定速率限制
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. 媒体要求
完整的平台限制
| 平台 | 图片最大尺寸 | 视频最大尺寸 | 宽高比 | 特殊要求 |
|---|---|---|---|---|
| 8MB | 快拍 100MB,Reels 300MB | 信息流 4:5 至 1.91:1,快拍 9:16 | 轮播最多 10 项 | |
| TikTok | 20MB | 4GB,3s-10min | 严格要求 9:16 | 照片轮播最多 35 项 |
| 5MB | 512MB,2min 20s | 灵活 | 1-4 张图片 | |
| 8MB | 5GB | 灵活 | 图片轮播最多 20 张 | |
| YouTube | 缩略图 2MB | 256GB | 首选 16:9 | 支持可恢复上传 |
| 10MB | 4GB | 灵活 | 多图最多 10 张 | |
| Threads | 8MB | 1GB,5min | 竖屏 9:16 | 轮播最多 10 项 |
| 32MB | 2GB | 灵活 | 需要封面图片 | |
| Bluesky | 1MB | 50MB,3min | 灵活 | AT Protocol |
| Snapchat | 20MB | 500MB | 9:16 | AES 加密 |
| Google Business | 5MB | N/A | 灵活 | 仅限图片 |
| 20MB | N/A | 灵活 | 通过 URL | |
| Telegram | 10MB | 50MB | 灵活 | 限制 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 | 不支持的视频格式 | 重新编码 |
| 2207042 | 超出每天 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 错误代码
| 代码 | 含义 | 操作 |
|---|---|---|
| ExpiredToken | JWT 已过期 | 自动刷新 |
| InvalidToken | 令牌无效 | 重新连接 |
| XRPCNotSupported | 应用密码不支持私信 | 使用完整身份验证 |
Reddit 错误代码
| 代码 | 含义 | 操作 |
|---|---|---|
| invalid token | 已过期 | 刷新 |
| not allowed to submit | Subreddit 限制 | 用户错误 |
| RATELIMIT | 操作过快 | 延迟后重试 |
| NO_LINKS | 不允许链接 | 用户错误 |
Telegram 错误代码
| 代码 | 含义 | 操作 |
|---|---|---|
| message too long | 超过 4096 个字符 | 截断 |
| 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 各等级的字符限制
| 等级 | 限制 |
|---|---|
| Free | 280 |
| Premium | 4,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 用户体验合规要求
TikTok 要求全面符合用户体验规范,才能通过 API 审核:
- 用户必须手动选择隐私级别(不设默认值)
- 必须显示评论、合拍和拼接开关
- 商业内容披露须经用户确认
- 上传前预览内容
- 声明明确同意
Reddit 用户代理要求
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 协议(Bluesky)
Bluesky 使用 AT Protocol,而非 OAuth:
关键概念:
- DID(去中心化标识符):用户的永久 ID(例如,
did:plc:abc123) - PDS(个人数据服务器):存储用户数据的位置
- Handle:便于人阅读的名称(例如,
user.bsky.social)
富文本 Facet:
提及和链接使用字节偏移量定位:
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. 快速调试清单
帖子发布失败时:
- 检查令牌是否有效 - 访问令牌是否已过期?
- 检查速率限制 - 是否达到每日/每小时限制?
- 检查媒体 URL - 平台能否直接获取媒体?
- 检查媒体规格 - 格式、大小、尺寸和宽高比是否正确?
- 检查 2207051 情况 - 是否在报错后仍然发布成功?
- 检查账户状态 - 账户是否受到限制?
- 检查 subreddit 规则 -(Reddit)是否允许链接?是否需要 Flair?
- 检查用户代理 -(Reddit)是否足够具有描述性?
- 检查机器人的权限 -(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. 平台对比矩阵
| 功能 | TikTok | YouTube | Threads | Bluesky | Snapchat | GBP | Telegram | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| OAuth | 两步 | 标准 | PKCE | 标准 | 标准 | 两步 | 基础 | AT Proto | 基础 | 标准 | Bot Token | ||
| 令牌有效期 | 60d | 2y+ | 2h+refresh | 365d refresh | 1y | 60d+ | 60d | N/A | 自动 | N/A | 1y | 永久 | N/A |
| 视频 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 否 | 否 | 是 |
| 轮播 | 10 | 35 | 4 | 20 | 无 | 10 | 10 | 无 | 4 | 无 | 无 | 图库 | 相册 10 |
| 定时发布 | API | 草稿 | 否 | API | API | API | API | 否 | API | 否 | 否 | 否 | 否 |
| 私信 | 是 | 否 | 是 | 是 | 否 | 是 | 否 | 否 | 是 | 是 | 否 | 是 | 是 |
| 分析 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 否 | 否 | 有限 | 否 | 有限 |
关于本指南
本指南由 Late 维护,内容基于跨全部 13 个平台安排 超过 100 万篇帖子 时总结出的真实模式编写。
这里记录的每个错误代码、边缘情况和特性都源自生产环境经验。Instagram 的 2207051 陷阱?我们经过数小时的调试才发现它。Snapchat 的加密要求?我们也是吃了苦头才学到的。
正在构建社交媒体应用? 如果你不想自己实现和维护所有这些集成,Late 的 API 可以为你处理复杂事项:通过一次集成,即可完成 OAuth、媒体处理、速率限制、错误处理,以及跨全部 13 个平台的内容安排。
由 Late 维护 - 面向开发者的社交媒体内容安排 API