DEV Community

Preecha
Preecha

Posted on

How to Use Hootsuite API ?

TL;DR

Hootsuite’s public API was deprecated in 2024. To build social media management workflows today, integrate directly with native platform APIs—such as Facebook Graph API, X/Twitter API v2, LinkedIn Marketing API, Instagram Graph API, and YouTube Data API. This guide shows how to implement OAuth 2.0, publish and schedule multi-platform posts, aggregate analytics, manage team permissions, and handle rate limits.

Try Apidog today

Note: Hootsuite previously exposed a public API with OAuth 2.0 authentication and endpoints for profiles, posts, analytics, and teams. Since its deprecation, use Hootsuite partner integrations, webhooks, or native social platform APIs for similar functionality.

Introduction

Hootsuite manages over 30 million social media accounts for 200,000+ businesses across 175+ countries. If you are building a social media dashboard, marketing platform, or reporting tool, direct API integrations let you automate publishing, engagement monitoring, and analytics collection.

For teams managing 20+ accounts, the core implementation goal is usually the same:

  1. Connect each social account with OAuth.
  2. Store and refresh tokens securely.
  3. Normalize post data into a shared internal format.
  4. Publish through platform-specific adapters.
  5. Queue scheduled posts and retries.
  6. Aggregate platform metrics into a reporting model.

Hootsuite API Status and Alternatives

Current API Situation

As of 2024, Hootsuite has deprecated its public API. Consider one of these approaches instead:

Approach Description Best for
Native platform APIs Direct integration with Facebook, X/Twitter, LinkedIn, and other platforms Full control and custom workflows
Audiense Hootsuite-owned audience intelligence Audience analysis
HeyOrca Social media scheduling API Content calendars
Buffer API Social media management Small teams
Sprout Social API Enterprise social management Large organizations
Agorapulse API Social media CRM Community management

Recommended Approach: Native Platform APIs

For most custom applications, native APIs provide the most control over publishing, account management, and analytics.

Platform API Typical capabilities
Facebook and Instagram Graph API Posts, insights, comments
X/Twitter API v2 Tweets, analytics, streams
LinkedIn Marketing API Posts, company pages, ads
Pinterest API v5 Pins, boards, analytics
TikTok Display API Videos and user info
YouTube Data API Videos, playlists, analytics

Getting Started: Multi-Platform Authentication

Step 1: Register Developer Apps

Create a developer application for every platform you support. Store app credentials in environment variables rather than source control.

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

Use separate redirect URIs where needed, for example:

https://your-app.example.com/oauth/facebook/callback
https://your-app.example.com/oauth/twitter/callback
https://your-app.example.com/oauth/linkedin/callback
Enter fullscreen mode Exit fullscreen mode

Step 2: Generate OAuth 2.0 Authorization URLs

Create a shared authorization URL builder, but keep scopes and endpoints platform-specific.

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'
        ].join(','),
        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];

  if (!config) {
    throw new Error(`Unsupported platform: ${platform}`);
  }

  return `${config.url}?${new URLSearchParams(config.params)}`;
};
Enter fullscreen mode Exit fullscreen mode

Generate and persist a state value before redirecting the user. Validate it in the callback to prevent authorization-request forgery.

Step 3: Exchange the Authorization Code for Tokens

After the platform redirects to your callback route, exchange the authorization code for an access token.

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 credentials = SOCIAL_CREDENTIALS[platform];

  const response = await fetch(tokenEndpoints[platform], {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({
      client_id:
        credentials.apiKey ||
        credentials.appId ||
        credentials.clientId,
      client_secret:
        credentials.appSecret ||
        credentials.apiSecret ||
        credentials.clientSecret,
      redirect_uri: credentials.redirectUri,
      code,
      grant_type: 'authorization_code'
    })
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed for ${platform}`);
  }

  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Store Tokens Securely

Store tokens per user and per connected social account. Encrypt access and refresh tokens at rest.

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', // Facebook or Instagram account mapping
  pageName: 'My Business Page'
};
Enter fullscreen mode Exit fullscreen mode

At minimum, persist:

  • The application user ID
  • Platform name
  • Encrypted access token
  • Encrypted refresh token, when provided
  • Token expiration time
  • Granted scopes
  • Connected page, organization, or account IDs

Post Scheduling and Publishing

Define a Shared Post Payload

Use one internal payload for your UI and scheduler, then translate it to each platform’s API format.

const postData = {
  message: 'New article: Building a multi-platform social publisher',
  link: 'https://example.com/articles/social-publisher',
  photo: 'https://example.com/images/social-publisher.png',
  platforms: ['facebook', 'twitter', 'linkedin', 'instagram'],
  facebookPageId: 'page_456',
  linkedinAuthorUrn: 'urn:li:organization:123',
  igAccountId: '17841400000000000'
};
Enter fullscreen mode Exit fullscreen mode

Create a Multi-Platform Publisher

Call each platform adapter only when the selected platform is present.

const createSocialPost = async (postData) => {
  const results = {};

  if (postData.platforms.includes('facebook')) {
    results.facebook = await postToFacebook({
      pageId: postData.facebookPageId,
      message: postData.message,
      link: postData.link,
      photo: postData.photo
    });
  }

  if (postData.platforms.includes('twitter')) {
    results.twitter = await postToTwitter({
      text: postData.message,
      media: postData.photo
    });
  }

  if (postData.platforms.includes('linkedin')) {
    results.linkedin = await postToLinkedIn({
      authorUrn: postData.linkedinAuthorUrn,
      text: postData.message,
      contentUrl: postData.link
    });
  }

  if (postData.platforms.includes('instagram')) {
    results.instagram = await postToInstagram({
      igAccountId: postData.igAccountId,
      imageUrl: postData.photo,
      caption: postData.message
    });
  }

  return results;
};
Enter fullscreen mode Exit fullscreen mode

In production, record the result separately for every platform. A post may succeed on Facebook and fail on LinkedIn, so avoid treating multi-platform publishing as all-or-nothing.

Publish to a Facebook Page

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}`,
    { method: 'POST' }
  );

  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

Publish to X/Twitter

Upload media first when the post includes an image, then include the returned media ID in the tweet request.

const postToTwitter = async (postData) => {
  const token = await getTwitterToken();

  let mediaIds = [];

  if (postData.media) {
    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();
};
Enter fullscreen mode Exit fullscreen mode

Publish to LinkedIn

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

Publish to Instagram

Instagram publishing uses a two-step flow: create a media container, then publish it.

const postToInstagram = async (postData) => {
  const token = await getInstagramToken();

  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();

  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

Schedule Posts with a Job Queue

Do not rely on in-process timers for scheduled publishing. Save the post in your database and enqueue a delayed job.

const schedulePost = async (postData, scheduledTime) => {
  const scheduledPost = await db.scheduledPosts.create({
    message: postData.message,
    platforms: postData.platforms,
    scheduledTime,
    status: 'pending',
    media: postData.media,
    link: postData.link
  });

  await jobQueue.add(
    'publish-social-post',
    { postId: scheduledPost.id },
    { delay: scheduledTime - Date.now() }
  );

  return scheduledPost;
};
Enter fullscreen mode Exit fullscreen mode

Process the queued job and persist the per-post result.

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

Track at least these states:

pending -> publishing -> published
                     -> failed
Enter fullscreen mode Exit fullscreen mode

Analytics and Reporting

Fetch Cross-Platform Analytics

Each platform returns metrics in a different shape. Fetch the raw responses first, then normalize them into a shared reporting format.

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)
  };

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

Fetch 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}`
  );

  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

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

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

Fetch 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}`,
    {
      headers: {
        Authorization: `Bearer ${token}`
      }
    }
  );

  return response.json();
};
Enter fullscreen mode Exit fullscreen mode

Use a normalization layer before calculating totals, because raw API response structures differ across platforms.

function sum(analytics, metric) {
  return Object.values(analytics).reduce((total, platform) => {
    return total + (platform.data?.[metric] || 0);
  }, 0);
}
Enter fullscreen mode Exit fullscreen mode

Team Management

Add Role-Based Access Control

A social publishing tool should restrict who can publish, approve, delete, or manage connected accounts.

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

Check permissions before sensitive actions:

if (!checkPermission(currentUser.role, 'approve_posts')) {
  throw new Error('You do not have permission to approve posts.');
}
Enter fullscreen mode Exit fullscreen mode

Rate Limiting

Platform Rate Limits

Rate limits differ by platform, endpoint, application type, and account plan. Use the applicable platform documentation as the source of truth.

Platform Limit Window
Facebook Graph 200 calls Per hour per user
X/Twitter API v2 300 tweets Per 15 minutes
LinkedIn 100–500 calls Per day
Instagram 200 calls Per hour

Implement Basic Rate Limit Handling

A central request wrapper prevents one platform adapter from exceeding its configured capacity.

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

For production workloads, also capture rate-limit response headers when platforms provide them and log failed requests for retries.

Production Deployment Checklist

Before going live, verify that your integration includes:

  • [ ] OAuth 2.0 flows for every supported platform
  • [ ] Encrypted token storage
  • [ ] Automatic token refresh where supported
  • [ ] Rate limiting per platform
  • [ ] API error handling and retry logic
  • [ ] Logging for API requests and responses
  • [ ] Post approval workflows
  • [ ] Content moderation
  • [ ] Analytics aggregation
  • [ ] Backup publishing mechanisms
  • [ ] Per-platform publishing status records
  • [ ] Monitoring for failed scheduled jobs

Real-World Use Cases

Social Media Dashboard

A marketing agency needs to manage 50+ client accounts across multiple platforms.

  • Challenge: Switching between platform dashboards and manually publishing content.
  • Implementation: Build a central dashboard with connected accounts, a unified composer, scheduled jobs, and aggregated analytics.
  • Result: 60% time savings and more consistent brand presence.

Automated Content Distribution

A publisher wants each new article distributed across social channels automatically.

  • Challenge: Manually sharing every article after publishing.
  • Implementation: Trigger a job when an article is published, generate a shared post payload, then send it through platform-specific publishing adapters.
  • Result: Instant distribution and 3x social traffic.

Conclusion

Although Hootsuite’s public API is deprecated, native platform APIs still provide the building blocks for social media management products.

The implementation priorities are:

  • Implement OAuth 2.0 separately for each platform.
  • Store access tokens securely and handle refresh flows.
  • Use platform-specific publishing adapters behind a shared post model.
  • Queue scheduled posts instead of relying on application timers.
  • Respect platform-specific rate limits.
  • Normalize analytics before building cross-platform reports.
  • Apply role-based permissions to publishing and team-management actions.
  • Use Apidog to streamline API testing and team collaboration.

FAQ

Does Hootsuite still have an API?

Hootsuite deprecated its public API in 2024. Use native platform APIs or alternative management platforms such as Buffer, Sprout Social, or Agorapulse.

How do I post to multiple platforms at once?

Implement OAuth for each platform, create a shared post payload, and call a platform-specific publishing function for every selected network.

What are the rate limits for social media APIs?

Limits vary by platform. Examples include Facebook at 200 calls per hour, X/Twitter at 300 tweets per 15 minutes, LinkedIn at 100–500 calls per day, and Instagram at 200 calls per hour.

How do I schedule posts?

Store posts with a scheduledTime in your database, then use a job queue such as Bull or Agenda to publish them at the scheduled time.

Can I get analytics from all platforms?

Yes. Each platform provides analytics APIs. Fetch platform-specific metrics, normalize the responses, and aggregate the results for cross-platform reporting.

Top comments (0)