DEV Community

Cover image for Hootsuite API 사용법
Rihpig
Rihpig

Posted on • Originally published at apidog.com

Hootsuite API 사용법

요약 (TL;DR)

Hootsuite API는 개발자가 소셜 미디어 관리 워크플로우를 프로그래밍 방식으로 통합할 수 있게 해줍니다. OAuth 2.0 인증, 프로필, 게시물, 분석 및 팀 관리를 위한 RESTful 엔드포인트를 사용하며, 요금제에 따라 분당 50-200개의 요청 제한이 있습니다. 이 가이드는 인증 설정, 게시물 예약, 분석 검색, 팀 관리 및 프로덕션 통합 전략을 다룹니다.

참고: Hootsuite는 2024년부로 공개 API를 중단했습니다. 이 가이드는 Hootsuite의 파트너 통합, 웹훅, 그리고 유사한 기능을 제공하는 타사 소셜 미디어 관리 API를 포함한 대체 접근 방식을 다룹니다.

지금 Apidog을 사용해 보세요

서론

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 트윗, 분석, 스트림
LinkedIn Marketing API 게시물, 회사 페이지, 광고
Pinterest 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
  }
};
Enter fullscreen mode Exit fullscreen mode

단계 2: OAuth 2.0 흐름 구현

플랫폼별 OAuth 2.0 인증 URL과 토큰 발급 핸들러를 구현합니다.

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();
};
Enter fullscreen mode Exit fullscreen mode

단계 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'
};
Enter fullscreen mode Exit fullscreen mode

게시물 예약 및 발행

다중 플랫폼 게시물 생성

여러 플랫폼에 동시에 게시하려면 다음과 같이 구현하세요.

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();
};
Enter fullscreen mode Exit fullscreen mode

게시물 예약

예약 게시를 구현하려면 작업 큐와 스케줄러를 사용하세요.

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;
  }
});
Enter fullscreen mode Exit fullscreen mode

분석 및 보고

교차 플랫폼 분석 가져오기

다수 플랫폼의 분석을 집계하여 활용하세요.

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);
}
Enter fullscreen mode Exit fullscreen mode

팀 관리

역할 기반 접근 제어

팀별로 역할과 권한을 관리하세요.

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);
};
Enter fullscreen mode Exit fullscreen mode

속도 제한

플랫폼 속도 제한

플랫폼 제한 기간
Facebook Graph 200 호출 사용자당 시간당
Twitter API v2 300 트윗 15분당
LinkedIn 100-500 호출 일일
Instagram 200 호출 시간당

속도 제한 처리 구현

실제 Rate Limiter를 아래와 같이 구현할 수 있습니다.

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++;
  }
}
Enter fullscreen mode Exit fullscreen mode

운영 환경 배포 체크리스트

서비스 개시 전 반드시 다음을 확인하세요:

  • [ ] 모든 플랫폼에 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를 제공합니다. 교차 플랫폼 보고를 위해 데이터를 집계하세요.

Top comments (0)