요약 (TL;DR)
Hootsuite API는 개발자가 소셜 미디어 관리 워크플로우를 프로그래밍 방식으로 통합할 수 있게 해줍니다. OAuth 2.0 인증, 프로필, 게시물, 분석 및 팀 관리를 위한 RESTful 엔드포인트를 사용하며, 요금제에 따라 분당 50-200개의 요청 제한이 있습니다. 이 가이드는 인증 설정, 게시물 예약, 분석 검색, 팀 관리 및 프로덕션 통합 전략을 다룹니다.
참고: Hootsuite는 2024년부로 공개 API를 중단했습니다. 이 가이드는 Hootsuite의 파트너 통합, 웹훅, 그리고 유사한 기능을 제공하는 타사 소셜 미디어 관리 API를 포함한 대체 접근 방식을 다룹니다.
서론
Hootsuite는 175개 이상의 국가에서 20만 개 이상의 기업을 위해 3천만 개 이상의 소셜 미디어 계정을 관리합니다. 소셜 미디어 도구, 마케팅 플랫폼 또는 분석 대시보드를 구축하는 개발자에게는 이 방대한 비즈니스 잠재 고객에게 도달하기 위해 소셜 미디어 API 통합이 필수적입니다.
현실은 이렇습니다. 20개 이상의 계정을 처리하는 소셜 미디어 관리자들은 수동 게시, 참여 추적 및 보고서 생성에 매주 25-35시간을 낭비합니다. 견고한 소셜 미디어 API 통합은 콘텐츠 배포, 참여 모니터링, 감성 분석 및 성과 보고를 자동화합니다.
Hootsuite API 현황 및 대안
현재 API 상황
2024년 현재 Hootsuite는 공개 API를 중단했습니다. 통합을 위한 옵션은 다음과 같습니다.
| 접근 방식 | 설명 | 최적 용도 |
|---|---|---|
| 네이티브 플랫폼 API | Facebook, Twitter, LinkedIn 등과의 직접 통합 | 완전한 제어, 맞춤형 솔루션 |
| Audiense | Hootsuite 소유의 잠재 고객 인텔리전스 | 잠재 고객 분석 |
| HeyOrca | 소셜 미디어 예약 API | 콘텐츠 달력 |
| Buffer API | 소셜 미디어 관리 | 소규모 팀 |
| Sprout Social API | 기업 소셜 관리 | 대규모 조직 |
| Agorapulse API | 소셜 미디어 CRM | 커뮤니티 관리 |
권장 접근 방식: 네이티브 플랫폼 API
대부분의 사용 사례에서 소셜 플랫폼과 직접 통합하는 것이 가장 큰 유연성을 제공합니다.
| 플랫폼 | API 이름 | 주요 기능 |
|---|---|---|
| Facebook/Instagram | Graph API | 게시물, 인사이트, 댓글 |
| Twitter/X | API v2 | 트윗, 분석, 스트림 |
| Marketing API | 게시물, 회사 페이지, 광고 | |
| API v5 | 핀, 보드, 분석 | |
| TikTok | Display API | 동영상, 사용자 정보 |
| YouTube | Data API | 동영상, 재생 목록, 분석 |
시작하기: 다중 플랫폼 인증
단계 1: 개발자 앱 등록
각 플랫폼에 대한 개발자 계정을 생성하세요.
// Store credentials securely
const SOCIAL_CREDENTIALS = {
facebook: {
appId: process.env.FB_APP_ID,
appSecret: process.env.FB_APP_SECRET,
redirectUri: process.env.FB_REDIRECT_URI
},
twitter: {
apiKey: process.env.TWITTER_API_KEY,
apiSecret: process.env.TWITTER_API_SECRET,
redirectUri: process.env.TWITTER_REDIRECT_URI
},
linkedin: {
clientId: process.env.LINKEDIN_CLIENT_ID,
clientSecret: process.env.LINKEDIN_CLIENT_SECRET,
redirectUri: process.env.LINKEDIN_REDIRECT_URI
},
instagram: {
appId: process.env.FB_APP_ID, // Uses Facebook Login
appSecret: process.env.FB_APP_SECRET
}
};
단계 2: OAuth 2.0 흐름 구현
여러 플랫폼을 위한 통합 OAuth 핸들러:
const getAuthUrl = (platform, state) => {
const configs = {
facebook: {
url: 'https://www.facebook.com/v18.0/dialog/oauth',
params: {
client_id: SOCIAL_CREDENTIALS.facebook.appId,
redirect_uri: SOCIAL_CREDENTIALS.facebook.redirectUri,
scope: 'pages_manage_posts,pages_read_engagement,instagram_basic,instagram_content_publish',
state
}
},
twitter: {
url: 'https://twitter.com/i/oauth2/authorize',
params: {
client_id: SOCIAL_CREDENTIALS.twitter.apiKey,
redirect_uri: SOCIAL_CREDENTIALS.twitter.redirectUri,
scope: 'tweet.read tweet.write users.read offline.access',
state,
response_type: 'code'
}
},
linkedin: {
url: 'https://www.linkedin.com/oauth/v2/authorization',
params: {
client_id: SOCIAL_CREDENTIALS.linkedin.clientId,
redirect_uri: SOCIAL_CREDENTIALS.linkedin.redirectUri,
scope: 'w_member_social r_basicprofile',
state,
response_type: 'code'
}
}
};
const config = configs[platform];
const params = new URLSearchParams(config.params);
return `${config.url}?${params.toString()}`;
};
// Handle OAuth callback
const handleOAuthCallback = async (platform, code) => {
const tokenEndpoints = {
facebook: 'https://graph.facebook.com/v18.0/oauth/access_token',
twitter: 'https://api.twitter.com/2/oauth2/token',
linkedin: 'https://www.linkedin.com/oauth/v2/accessToken'
};
const response = await fetch(tokenEndpoints[platform], {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: SOCIAL_CREDENTIALS[platform].apiKey || SOCIAL_CREDENTIALS[platform].appId || SOCIAL_CREDENTIALS[platform].clientId,
client_secret: SOCIAL_CREDENTIALS[platform].appSecret || SOCIAL_CREDENTIALS[platform].apiSecret,
redirect_uri: SOCIAL_CREDENTIALS[platform].redirectUri,
code,
grant_type: 'authorization_code'
})
});
return response.json();
};
단계 3: 토큰을 안전하게 저장
// Database schema for social tokens
const SocialToken = {
userId: 'user_123',
platform: 'facebook',
accessToken: 'encrypted_token_here',
refreshToken: 'encrypted_refresh_token',
tokenExpiry: Date.now() + 5183999, // 60 days
scopes: ['pages_manage_posts', 'pages_read_engagement'],
pageId: 'page_456', // For Facebook/Instagram
pageName: 'My Business Page'
};
게시물 예약 및 발행
다중 플랫폼 게시물 생성
여러 플랫폼에 동시에 게시:
const createSocialPost = async (postData) => {
const results = {};
// Facebook Page Post
if (postData.platforms.includes('facebook')) {
results.facebook = await postToFacebook({
pageId: postData.facebookPageId,
message: postData.message,
link: postData.link,
photo: postData.photo
});
}
// Twitter Post
if (postData.platforms.includes('twitter')) {
results.twitter = await postToTwitter({
text: postData.message,
media: postData.photo
});
}
// LinkedIn Post
if (postData.platforms.includes('linkedin')) {
results.linkedin = await postToLinkedIn({
authorUrn: postData.linkedinAuthorUrn,
text: postData.message,
contentUrl: postData.link
});
}
// Instagram Post
if (postData.platforms.includes('instagram')) {
results.instagram = await postToInstagram({
igAccountId: postData.igAccountId,
imageUrl: postData.photo,
caption: postData.message
});
}
return results;
};
// Facebook posting
const postToFacebook = async (postData) => {
const token = await getFacebookPageToken(postData.pageId);
const params = new URLSearchParams({
message: postData.message,
access_token: token
});
if (postData.link) {
params.append('link', postData.link);
}
if (postData.photo) {
params.append('photo', postData.photo);
}
const response = await fetch(
`https://graph.facebook.com/v18.0/${postData.pageId}/feed?${params.toString()}`,
{ method: 'POST' }
);
return response.json();
};
// Twitter posting
const postToTwitter = async (postData) => {
const token = await getTwitterToken();
let mediaIds = [];
if (postData.media) {
// Upload media first
const mediaUpload = await uploadTwitterMedia(postData.media, token);
mediaIds = [mediaUpload.media_id_string];
}
const response = await fetch('https://api.twitter.com/2/tweets', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
text: postData.text,
media: mediaIds.length > 0 ? { media_ids: mediaIds } : undefined
})
});
return response.json();
};
// LinkedIn posting
const postToLinkedIn = async (postData) => {
const token = await getLinkedInToken();
const post = {
author: postData.authorUrn,
lifecycleState: 'PUBLISHED',
specificContent: {
'com.linkedin.ugc.ShareContent': {
shareCommentary: {
text: postData.text
},
shareMediaCategory: postData.contentUrl ? 'ARTICLE' : 'NONE',
media: postData.contentUrl ? [{
status: 'READY',
media: postData.contentUrn,
description: { text: postData.text }
}] : []
}
},
visibility: {
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC'
}
};
const response = await fetch('https://api.linkedin.com/v2/ugcPosts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Restli-Protocol-Version': '2.0.0'
},
body: JSON.stringify(post)
});
return response.json();
};
// Instagram posting
const postToInstagram = async (postData) => {
const token = await getInstagramToken();
// Step 1: Create media container
const containerResponse = await fetch(
`https://graph.facebook.com/v18.0/${postData.igAccountId}/media`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({
image_url: postData.imageUrl,
caption: postData.caption
})
}
);
const container = await containerResponse.json();
// Step 2: Publish
const publishResponse = await fetch(
`https://graph.facebook.com/v18.0/${postData.igAccountId}/media_publish`,
{
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ creation_id: container.id })
}
);
return publishResponse.json();
};
게시물 예약
게시물 예약 구현:
const schedulePost = async (postData, scheduledTime) => {
// Store in database for later execution
const scheduledPost = await db.scheduledPosts.create({
message: postData.message,
platforms: postData.platforms,
scheduledTime: scheduledTime,
status: 'pending',
media: postData.media,
link: postData.link
});
// Set up job queue
await jobQueue.add('publish-social-post', {
postId: scheduledPost.id
}, {
delay: scheduledTime - Date.now()
});
return scheduledPost;
};
// Job processor
jobQueue.process('publish-social-post', async (job) => {
const post = await db.scheduledPosts.findById(job.data.postId);
try {
const result = await createSocialPost(post);
await db.scheduledPosts.update(post.id, {
status: 'published',
publishedAt: new Date(),
results: result
});
return result;
} catch (error) {
await db.scheduledPosts.update(post.id, {
status: 'failed',
error: error.message
});
throw error;
}
});
분석 및 보고
교차 플랫폼 분석 가져오기
모든 플랫폼의 지표 집계:
const getSocialAnalytics = async (accountId, dateRange) => {
const analytics = {
facebook: await getFacebookAnalytics(accountId.facebook, dateRange),
twitter: await getTwitterAnalytics(accountId.twitter, dateRange),
linkedin: await getLinkedInAnalytics(accountId.linkedin, dateRange),
instagram: await getInstagramAnalytics(accountId.instagram, dateRange)
};
// Aggregate metrics
const totals = {
impressions: sum(analytics, 'impressions'),
engagement: sum(analytics, 'engagement'),
clicks: sum(analytics, 'clicks'),
shares: sum(analytics, 'shares'),
comments: sum(analytics, 'comments'),
newFollowers: sum(analytics, 'newFollowers')
};
return { analytics, totals };
};
// Facebook Insights
const getFacebookAnalytics = async (pageId, dateRange) => {
const token = await getFacebookPageToken(pageId);
const metrics = [
'page_impressions_unique',
'page_engaged_users',
'page_post_engagements',
'page_clicks',
'page_fan_adds'
];
const params = new URLSearchParams({
metric: metrics.join(','),
since: dateRange.from,
until: dateRange.until,
access_token: token
});
const response = await fetch(
`https://graph.facebook.com/v18.0/${pageId}/insights?${params.toString()}`
);
return response.json();
};
// Twitter Analytics
const getTwitterAnalytics = async (userId, dateRange) => {
const token = await getTwitterToken();
const response = await fetch(
`https://api.twitter.com/2/users/${userId}/metrics/private`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
return response.json();
};
// LinkedIn Analytics
const getLinkedInAnalytics = async (organizationId, dateRange) => {
const token = await getLinkedInToken();
const response = await fetch(
`https://api.linkedin.com/v2/organizationalEntityFollowerStatistics?q=organizationalEntity&organizationalEntity=${organizationId}`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
return response.json();
};
// Instagram Insights
const getInstagramAnalytics = async (igAccountId, dateRange) => {
const token = await getInstagramToken();
const metrics = [
'impressions',
'reach',
'engagement',
'profile_views',
'follower_count'
];
const params = new URLSearchParams({
metric: metrics.join(','),
period: 'day',
since: dateRange.from,
until: dateRange.until
});
const response = await fetch(
`https://graph.facebook.com/v18.0/${igAccountId}/insights?${params.toString()}`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
return response.json();
};
function sum(analytics, metric) {
return Object.values(analytics).reduce((total, platform) => {
return total + (platform.data?.[metric] || 0);
}, 0);
}
팀 관리
역할 기반 접근 제어
const TEAM_ROLES = {
ADMIN: 'admin',
MANAGER: 'manager',
CONTRIBUTOR: 'contributor',
VIEWER: 'viewer'
};
const ROLE_PERMISSIONS = {
[TEAM_ROLES.ADMIN]: ['create', 'read', 'update', 'delete', 'manage_team', 'billing'],
[TEAM_ROLES.MANAGER]: ['create', 'read', 'update', 'approve_posts'],
[TEAM_ROLES.CONTRIBUTOR]: ['create', 'read'],
[TEAM_ROLES.VIEWER]: ['read']
};
const checkPermission = (userRole, requiredPermission) => {
const permissions = ROLE_PERMISSIONS[userRole] || [];
return permissions.includes(requiredPermission);
};
속도 제한
플랫폼 속도 제한
| 플랫폼 | 제한 | 기간 |
|---|---|---|
| Facebook Graph | 200 호출 | 사용자당 시간당 |
| Twitter API v2 | 300 트윗 | 15분당 |
| 100-500 호출 | 일일 | |
| 200 호출 | 시간당 |
속도 제한 처리 구현
class SocialMediaRateLimiter {
constructor() {
this.limits = {
facebook: { limit: 200, window: 3600000 },
twitter: { limit: 300, window: 900000 },
linkedin: { limit: 500, window: 86400000 },
instagram: { limit: 200, window: 3600000 }
};
this.counters = {};
}
async request(platform, endpoint, options) {
await this.waitForCapacity(platform);
const response = await fetch(endpoint, options);
this.incrementCounter(platform);
return response;
}
async waitForCapacity(platform) {
const limit = this.limits[platform];
const counter = this.counters[platform] || { count: 0, resetTime: Date.now() };
if (Date.now() > counter.resetTime + limit.window) {
counter.count = 0;
counter.resetTime = Date.now();
}
if (counter.count >= limit.limit) {
const waitTime = counter.resetTime + limit.window - Date.now();
await new Promise(resolve => setTimeout(resolve, waitTime));
}
this.counters[platform] = counter;
}
incrementCounter(platform) {
if (!this.counters[platform]) {
this.counters[platform] = { count: 0, resetTime: Date.now() };
}
this.counters[platform].count++;
}
}
운영 환경 배포 체크리스트
서비스 개시 전:
- [ ] 모든 플랫폼에 OAuth 2.0 구현
- [ ] 토큰을 암호화하여 안전하게 저장
- [ ] 자동 토큰 갱신 설정
- [ ] 플랫폼별 속도 제한 구현
- [ ] 포괄적인 오류 처리 추가
- [ ] 모든 API 호출에 대한 로깅 설정
- [ ] 게시물 승인 워크플로우 생성
- [ ] 콘텐츠 조정 구현
- [ ] 분석 집계 설정
- [ ] 백업 게시 메커니즘 생성
실제 사용 사례
소셜 미디어 대시보드
마케팅 에이전시가 통합 대시보드를 구축:
- 과제: 여러 플랫폼에 걸쳐 50개 이상의 클라이언트 계정 관리
- 해결책: 다중 플랫폼 게시 기능을 갖춘 중앙 대시보드
- 결과: 시간 60% 절약, 일관된 브랜드 존재감
자동화된 콘텐츠 배포
출판사가 기사 공유를 자동화:
- 과제: 새 콘텐츠의 수동 공유
- 해결책: 새 기사를 모든 플랫폼에 자동 게시
- 결과: 즉각적인 배포, 소셜 트래픽 3배 증가
결론
Hootsuite의 공개 API는 중단되었지만, 네이티브 플랫폼 API는 포괄적인 소셜 미디어 관리 기능을 제공합니다. 핵심 요약:
- 각 플랫폼에 대해 OAuth 2.0을 별도로 구현
- 속도 제한은 플랫폼마다 크게 다름
- 통합 게시를 위해서는 플랫폼별 구현이 필요함
- 분석 집계는 교차 플랫폼 인사이트를 제공함
- Apidog는 API 테스트 및 팀 협업을 간소화함
FAQ 섹션
Hootsuite는 여전히 API를 제공하나요?
Hootsuite는 2024년에 공개 API를 중단했습니다. 네이티브 플랫폼 API 또는 Buffer, Sprout Social, Agorapulse와 같은 대체 관리 플랫폼을 사용하세요.
여러 플랫폼에 동시에 게시하려면 어떻게 해야 하나요?
각 플랫폼에 대한 OAuth를 구현하고, 각 플랫폼의 API를 병렬로 호출하는 통합 게시 함수를 생성하세요.
소셜 미디어 API의 속도 제한은 무엇인가요?
제한은 다양합니다: Facebook (시간당 200회), Twitter (15분당 300회), LinkedIn (일일 100-500회), Instagram (시간당 200회).
게시물을 예약하려면 어떻게 해야 하나요?
예약 시간과 함께 게시물을 데이터베이스에 저장한 다음, 작업 큐(Bull, Agenda)를 사용하여 예약된 시간에 게시하세요.
모든 플랫폼에서 분석 데이터를 얻을 수 있나요?
네, 각 플랫폼은 분석 API를 제공합니다. 교차 플랫폼 보고를 위해 데이터를 집계하세요.
