Integrate Bolt.new with social media management by using native social platform APIs directly or building a Hootsuite plugin via their App Directory SDK. Hootsuite's full REST API requires partner access — for most Bolt apps, connect Facebook, Twitter/X, and LinkedIn APIs directly from a Next.js API route. Deploy to Netlify or Bolt Cloud before testing social post scheduling, as webhooks and OAuth callbacks require a public URL.
Build a Social Media Dashboard in Bolt.new with Direct Platform APIs
Hootsuite is one of the most recognized brands in social media management, but its REST API access is gated behind a partner program — making direct Hootsuite API integration unavailable to most individual developers and small teams building in Bolt.new. Rather than waiting for partner approval, the more practical and powerful approach is to connect directly to the social media platforms that Hootsuite itself manages: Facebook Graph API, Twitter/X API v2, and LinkedIn API. This gives you full control, no intermediary costs, and the ability to build features tailored to your specific use case.
Bolt.new's WebContainer is well-suited for this pattern. The native `fetch()` API and standard HTTP client libraries like `axios` work perfectly for outbound calls to social platform REST APIs. You can build and test the API integration logic in the Bolt preview. The one constraint to plan around: OAuth callback flows for social platforms require a stable, registered redirect URI — these cannot use the dynamic preview URL that Bolt's WebContainer generates. Deploy to Netlify early so your OAuth flows work properly.
For teams that already have Hootsuite and want to build custom internal tools on top of it — reporting dashboards, approval workflows, content calendar integrations — Hootsuite's App Directory SDK is the relevant path. It lets you build custom apps that run inside the Hootsuite interface. But for most Bolt developers building standalone products, connecting to social APIs directly gives you more flexibility and avoids the API access barrier.
Integration method
Hootsuite's full REST API requires partner access, which makes direct Hootsuite API integration impractical for most Bolt developers. The practical path is to build a custom social management dashboard using native social media platform APIs (Facebook Graph API, Twitter/X API v2, LinkedIn API) directly from Next.js API routes. Store all API keys and tokens in server-side environment variables — never in client-side code. OAuth callback flows for social platforms require a deployed URL, not the Bolt preview.
Prerequisites
- A Bolt.new account with a new Next.js project open
- Twitter/X Developer account at developer.twitter.com — create an app to get a Bearer Token for read operations or OAuth 2.0 credentials for write operations
- A Facebook Developer account at developers.facebook.com with a Facebook App configured and a Page Access Token for your Facebook Page
- A LinkedIn Developer account at developer.linkedin.com with an app approved for the Marketing Developer Platform or Community Management API
- A Netlify account for deployment — required for OAuth callback flows and any webhook-based features
Step-by-step guide
Understand Hootsuite API access and choose your integration approach
Understand Hootsuite API access and choose your integration approach
Before writing any code, it is important to understand what is and is not possible with Hootsuite's API. Hootsuite has two distinct developer offerings, and they serve very different use cases. The first option is the Hootsuite App Directory SDK, which lets you build custom apps that run inside the Hootsuite interface. These are iframe-based applications embedded in the Hootsuite sidebar or stream. If your goal is to build a tool that Hootsuite users install from within Hootsuite itself, this is the right path. It requires signing up as a Hootsuite technology partner at developer.hootsuite.com. The second option is Hootsuite's REST API, which provides programmatic access to social publishing, analytics, and account management. However, full API access requires completing Hootsuite's partner onboarding process. Consumer-level API keys are not publicly available in the way Twitter or LinkedIn provide developer credentials. For the majority of Bolt developers building standalone social management tools or dashboards for their own apps, the practical path is to connect directly to the social media platforms — Facebook Graph API, Twitter/X API v2, and LinkedIn API. This is actually more powerful: you get direct access to platform data without routing through an intermediary, you avoid Hootsuite API rate limits on top of platform rate limits, and you can build features Hootsuite's API does not expose. Decide which platforms you need, create developer accounts on each, and gather your API credentials before proceeding. Twitter/X requires a Developer Portal app, Facebook requires a Facebook App with Page permissions, and LinkedIn requires a LinkedIn App with the appropriate products approved.
Set up my Next.js project for social media API integration. Create a .env.local file with these placeholder variables: TWITTER_BEARER_TOKEN=your-token-here, TWITTER_CLIENT_ID=your-client-id, TWITTER_CLIENT_SECRET=your-client-secret, LINKEDIN_ACCESS_TOKEN=your-token-here, LINKEDIN_CLIENT_ID=your-client-id, LINKEDIN_CLIENT_SECRET=your-client-secret, FACEBOOK_PAGE_TOKEN=your-token-here, FACEBOOK_PAGE_ID=your-page-id, FACEBOOK_APP_ID=your-app-id, FACEBOOK_APP_SECRET=your-app-secret. Create a lib/social-apis.ts file that exports typed helper functions for each platform with proper error handling. Add TypeScript interfaces for SocialPost and PlatformStatus.
Paste this in Bolt.new chat
1// lib/social-apis.ts2export interface SocialPost {3 content: string;4 mediaUrls?: string[];5 scheduledAt?: Date;6}78export interface PublishResult {9 platform: string;10 success: boolean;11 postId?: string;12 error?: string;13}1415// Twitter/X API v216export async function publishToTwitter(content: string): Promise<PublishResult> {17 const token = process.env.TWITTER_BEARER_TOKEN;18 if (!token) return { platform: 'twitter', success: false, error: 'Missing TWITTER_BEARER_TOKEN' };1920 try {21 const response = await fetch('https://api.twitter.com/2/tweets', {22 method: 'POST',23 headers: {24 'Authorization': `Bearer ${token}`,25 'Content-Type': 'application/json',26 },27 body: JSON.stringify({ text: content }),28 });29 const data = await response.json();30 if (!response.ok) throw new Error(data.detail || 'Twitter API error');31 return { platform: 'twitter', success: true, postId: data.data?.id };32 } catch (error) {33 return { platform: 'twitter', success: false, error: (error as Error).message };34 }35}3637// LinkedIn API38export async function publishToLinkedIn(content: string, authorUrn: string): Promise<PublishResult> {39 const token = process.env.LINKEDIN_ACCESS_TOKEN;40 if (!token) return { platform: 'linkedin', success: false, error: 'Missing LINKEDIN_ACCESS_TOKEN' };4142 try {43 const response = await fetch('https://api.linkedin.com/v2/ugcPosts', {44 method: 'POST',45 headers: {46 'Authorization': `Bearer ${token}`,47 'Content-Type': 'application/json',48 'X-Restli-Protocol-Version': '2.0.0',49 },50 body: JSON.stringify({51 author: authorUrn,52 lifecycleState: 'PUBLISHED',53 specificContent: {54 'com.linkedin.ugc.ShareContent': {55 shareCommentary: { text: content },56 shareMediaCategory: 'NONE',57 },58 },59 visibility: { 'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC' },60 }),61 });62 const data = await response.json();63 if (!response.ok) throw new Error(JSON.stringify(data));64 return { platform: 'linkedin', success: true, postId: data.id };65 } catch (error) {66 return { platform: 'linkedin', success: false, error: (error as Error).message };67 }68}6970// Facebook Graph API71export async function publishToFacebook(content: string): Promise<PublishResult> {72 const token = process.env.FACEBOOK_PAGE_TOKEN;73 const pageId = process.env.FACEBOOK_PAGE_ID;74 if (!token || !pageId) return { platform: 'facebook', success: false, error: 'Missing Facebook credentials' };7576 try {77 const response = await fetch(78 `https://graph.facebook.com/v18.0/${pageId}/feed`,79 {80 method: 'POST',81 headers: { 'Content-Type': 'application/json' },82 body: JSON.stringify({ message: content, access_token: token }),83 }84 );85 const data = await response.json();86 if (data.error) throw new Error(data.error.message);87 return { platform: 'facebook', success: true, postId: data.id };88 } catch (error) {89 return { platform: 'facebook', success: false, error: (error as Error).message };90 }91}Pro tip: Twitter/X Bearer Tokens are read-only — they can fetch tweets and user data but cannot post. To create tweets, you need OAuth 2.0 user context credentials (Client ID + Client Secret) with the tweet.write scope. Check the Twitter Developer Portal docs for which credential type you need for your use case.
Expected result: A lib/social-apis.ts helper with typed functions for publishing to Twitter/X, LinkedIn, and Facebook, with API keys configured in .env.local.
Build the social post publishing API route
Build the social post publishing API route
The post publishing route is the core of your social dashboard. It receives a request from your React frontend with the post content, target platforms, and optional scheduling information, then dispatches to each selected platform's API in parallel. Running platform publishes in parallel with `Promise.allSettled()` is important — it ensures a failure on one platform (say, a LinkedIn token expiry) does not prevent successful publishing to the other platforms. The `allSettled()` variant (not `Promise.all()`) is specifically designed for this: it resolves even when some promises reject, giving you individual results per platform. Error handling at this layer should distinguish between recoverable errors (rate limits, temporary API unavailability) and permanent errors (invalid tokens, permission issues). Return a structured response that tells the frontend exactly which platforms succeeded and which failed, so users can retry individual platforms if needed. For post content, be mindful of platform character limits: Twitter/X enforces 280 characters (or 25,000 for Twitter Blue subscribers), LinkedIn has a 3,000 character limit for text posts, and Facebook Pages have very generous limits. Consider adding client-side character count validation in your React component to catch limit violations before the API call.
Create a social media publishing API route at app/api/social/publish/route.ts. Accept POST with JSON body: content (string, required), platforms (array of 'twitter' | 'linkedin' | 'facebook', required), linkedinAuthorUrn (string, optional). Import publishToTwitter, publishToLinkedIn, publishToFacebook from lib/social-apis.ts. Run selected platform publishes in parallel using Promise.allSettled(). Return a JSON response with an array of results per platform showing success/failure and postId. Add content validation: reject if content is empty or over 280 chars when Twitter is in platforms. Also create a React component SocialComposer with a textarea, platform checkboxes for Twitter/LinkedIn/Facebook, character counter that changes to red near platform limits, and submit handling with per-platform status indicators.
Paste this in Bolt.new chat
1// app/api/social/publish/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { publishToTwitter, publishToLinkedIn, publishToFacebook } from '@/lib/social-apis';45type Platform = 'twitter' | 'linkedin' | 'facebook';67interface PublishRequest {8 content: string;9 platforms: Platform[];10 linkedinAuthorUrn?: string;11}1213export async function POST(request: NextRequest) {14 const body: PublishRequest = await request.json();15 const { content, platforms, linkedinAuthorUrn } = body;1617 if (!content?.trim()) {18 return NextResponse.json({ error: 'Content is required' }, { status: 400 });19 }2021 if (!platforms?.length) {22 return NextResponse.json({ error: 'At least one platform is required' }, { status: 400 });23 }2425 if (platforms.includes('twitter') && content.length > 280) {26 return NextResponse.json(27 { error: 'Content exceeds Twitter 280 character limit' },28 { status: 400 }29 );30 }3132 const publishPromises = platforms.map((platform) => {33 switch (platform) {34 case 'twitter':35 return publishToTwitter(content);36 case 'linkedin':37 return publishToLinkedIn(content, linkedinAuthorUrn || process.env.LINKEDIN_PERSON_URN || '');38 case 'facebook':39 return publishToFacebook(content);40 default:41 return Promise.resolve({ platform, success: false, error: 'Unknown platform' });42 }43 });4445 const results = await Promise.allSettled(publishPromises);4647 const formattedResults = results.map((result, index) => {48 if (result.status === 'fulfilled') return result.value;49 return { platform: platforms[index], success: false, error: result.reason?.message || 'Unknown error' };50 });5152 const hasSuccess = formattedResults.some((r) => r.success);53 const statusCode = hasSuccess ? 200 : 500;5455 return NextResponse.json({ results: formattedResults }, { status: statusCode });56}Pro tip: LinkedIn's UGC Posts API requires the author URN in the format urn:li:person:{personId} or urn:li:organization:{orgId}. For personal profiles, fetch the person ID from GET https://api.linkedin.com/v2/me using the access token. Store it as LINKEDIN_PERSON_URN in your environment variables to avoid fetching it on every publish call.
Expected result: A working API route that publishes social posts to multiple platforms in parallel and returns per-platform success/failure results.
Build the social analytics fetching route
Build the social analytics fetching route
Analytics retrieval is typically read-only and is the easiest part of the social dashboard to implement, since it does not require write permissions or complex OAuth flows for fetching public metrics. Each platform exposes different data structures and terminology, so normalizing the response into a consistent format is valuable for building a unified dashboard UI. Twitter/X API v2 provides tweet metrics (impressions, engagements, likes, retweets) via the `tweet.fields=public_metrics` query parameter when fetching tweets. LinkedIn's analytics API uses a more complex query structure with `timeGranularity`, `dateRange`, and `aggregations` parameters that differ from LinkedIn's UGC Posts API. Facebook Graph API provides page insights through the `/page-id/insights` endpoint with configurable metric names and periods. Caching analytics data in Supabase is strongly recommended rather than fetching live on every page load. Platform APIs have rate limits — LinkedIn's marketing analytics endpoint allows only 100 calls per day per app on the basic tier. A scheduled background job (or a manual 'Refresh' button with a cooldown) that fetches and stores analytics periodically is more robust and faster for the user than on-demand platform API calls. For the initial build, focus on the metrics that matter most for your use case: follower growth trends and engagement rates are universally useful. Bolt can generate Recharts visualizations for these metrics from the stored Supabase data.
Create a social media analytics API route at app/api/social/analytics/route.ts. Accept GET with query params: platform (twitter|linkedin|facebook), period (7d|30d|90d). For Twitter: fetch recent tweets using TWITTER_BEARER_TOKEN at GET https://api.twitter.com/2/users/:id/tweets with tweet.fields=public_metrics,created_at (use TWITTER_USER_ID env var). For Facebook: fetch page insights at https://graph.facebook.com/v18.0/:pageId/insights with metric=page_post_engagements,page_impressions using FACEBOOK_PAGE_TOKEN and FACEBOOK_PAGE_ID. Return normalized data with totalImpressions, totalEngagements, and a posts array. Also create a AnalyticsDashboard React component with platform tabs and line charts using Recharts showing impressions over time.
Paste this in Bolt.new chat
1// app/api/social/analytics/route.ts2import { NextRequest, NextResponse } from 'next/server';34interface NormalizedAnalytics {5 platform: string;6 period: string;7 totalImpressions: number;8 totalEngagements: number;9 posts: Array<{10 id: string;11 content: string;12 publishedAt: string;13 impressions: number;14 engagements: number;15 }>;16}1718async function fetchTwitterAnalytics(period: string): Promise<NormalizedAnalytics> {19 const userId = process.env.TWITTER_USER_ID!;20 const token = process.env.TWITTER_BEARER_TOKEN!;21 const maxResults = period === '7d' ? 10 : period === '30d' ? 50 : 100;2223 const url = `https://api.twitter.com/2/users/${userId}/tweets?max_results=${maxResults}&tweet.fields=public_metrics,created_at&expansions=attachments.media_keys`;24 const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });25 const data = await response.json();2627 if (!response.ok) throw new Error(data.detail || 'Twitter API error');2829 const posts = (data.data || []).map((tweet: { id: string; text: string; created_at: string; public_metrics: { impression_count: number; like_count: number; retweet_count: number; reply_count: number } }) => ({30 id: tweet.id,31 content: tweet.text,32 publishedAt: tweet.created_at,33 impressions: tweet.public_metrics?.impression_count || 0,34 engagements: (tweet.public_metrics?.like_count || 0) +35 (tweet.public_metrics?.retweet_count || 0) +36 (tweet.public_metrics?.reply_count || 0),37 }));3839 return {40 platform: 'twitter',41 period,42 totalImpressions: posts.reduce((sum: number, p: { impressions: number }) => sum + p.impressions, 0),43 totalEngagements: posts.reduce((sum: number, p: { engagements: number }) => sum + p.engagements, 0),44 posts,45 };46}4748async function fetchFacebookAnalytics(period: string): Promise<NormalizedAnalytics> {49 const pageId = process.env.FACEBOOK_PAGE_ID!;50 const token = process.env.FACEBOOK_PAGE_TOKEN!;51 const periodParam = period === '7d' ? 'week' : 'month';5253 const url = `https://graph.facebook.com/v18.0/${pageId}/insights?metric=page_impressions,page_post_engagements&period=${periodParam}&access_token=${token}`;54 const response = await fetch(url);55 const data = await response.json();5657 if (data.error) throw new Error(data.error.message);5859 const impressionsData = data.data?.find((m: { name: string }) => m.name === 'page_impressions');60 const engagementsData = data.data?.find((m: { name: string }) => m.name === 'page_post_engagements');6162 const totalImpressions = impressionsData?.values?.reduce((sum: number, v: { value: number }) => sum + (v.value || 0), 0) || 0;63 const totalEngagements = engagementsData?.values?.reduce((sum: number, v: { value: number }) => sum + (v.value || 0), 0) || 0;6465 return { platform: 'facebook', period, totalImpressions, totalEngagements, posts: [] };66}6768export async function GET(request: NextRequest) {69 const { searchParams } = new URL(request.url);70 const platform = searchParams.get('platform') || 'twitter';71 const period = searchParams.get('period') || '7d';7273 try {74 let analytics: NormalizedAnalytics;75 if (platform === 'twitter') {76 analytics = await fetchTwitterAnalytics(period);77 } else if (platform === 'facebook') {78 analytics = await fetchFacebookAnalytics(period);79 } else {80 return NextResponse.json({ error: 'Platform not supported' }, { status: 400 });81 }82 return NextResponse.json(analytics);83 } catch (error) {84 return NextResponse.json(85 { error: (error as Error).message },86 { status: 500 }87 );88 }89}Pro tip: Twitter/X API v2 requires a Twitter Developer account with Elevated access to fetch tweet analytics metrics like impression_count. Basic access (free tier) has very limited metric visibility. Check your access level in the Twitter Developer Portal before building analytics features.
Expected result: An analytics API route that fetches normalized performance data from Twitter/X and Facebook, ready to display in a dashboard with charts.
Deploy to Netlify and set up environment variables for production
Deploy to Netlify and set up environment variables for production
Deploying your social dashboard to Netlify is required for several important reasons beyond just going to production. First, OAuth flows for social platforms require a stable, registered redirect URI — the dynamic WebContainer preview URL changes and cannot be registered with Twitter's, LinkedIn's, or Facebook's developer consoles. Second, some social platform APIs enforce CORS restrictions that only affect browser-based requests, and your Next.js API routes (which are server-side) bypass these automatically once deployed. Third, any webhook or notification features from social platforms require an accessible public URL. To deploy: in Bolt.new, click the Deploy button in the top-right, connect to Netlify via OAuth, and wait for the initial deploy. Then open the Netlify dashboard for your site, navigate to Site Configuration → Environment Variables, and add every variable from your .env.local file: TWITTER_BEARER_TOKEN, TWITTER_USER_ID, LINKEDIN_ACCESS_TOKEN, LINKEDIN_PERSON_URN, LINKEDIN_CLIENT_ID, LINKEDIN_CLIENT_SECRET, FACEBOOK_PAGE_TOKEN, FACEBOOK_PAGE_ID, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET. After adding the variables, trigger a redeploy from Netlify's Deploys tab. Update OAuth redirect URIs in each platform's developer console to include your Netlify URL. For Twitter/X: update the Callback URI in your app settings at developer.twitter.com. For LinkedIn: update Authorized Redirect URLs in your LinkedIn app. For Facebook: add your Netlify domain to the Valid OAuth Redirect URIs in the Facebook App settings. Remember: Bolt.new's WebContainer cannot receive incoming webhook requests during development. This is a core architectural constraint — the browser-based runtime has no persistent public URL. Social platform notifications, incoming webhook events, and OAuth callbacks will only work reliably on your deployed Netlify domain.
Add a netlify.toml file to my project root with the correct Next.js 14+ build configuration. Include the build command 'npm run build', publish directory '.next', and the @netlify/plugin-nextjs plugin. Also add a deployment checklist page visible only in development at /dev/deploy-checklist that lists all required environment variables for the social dashboard, shows which ones are currently set (without revealing values), and includes the exact Netlify dashboard URL path for adding env vars.
Paste this in Bolt.new chat
1# netlify.toml2[build]3 command = "npm run build"4 publish = ".next"56[[plugins]]7 package = "@netlify/plugin-nextjs"89[build.environment]10 NODE_VERSION = "20"11 NEXT_TELEMETRY_DISABLED = "1"1213[[redirects]]14 from = "/api/*"15 to = "/api/:splat"16 status = 200Pro tip: After deploying, test each social platform API integration independently before testing the full dashboard. Start with a simple GET request to verify your credentials work, then move on to publishing. This makes it easier to isolate authentication issues when setting up for the first time.
Expected result: A live Netlify deployment with all social API credentials configured, OAuth redirect URIs updated in each platform's developer console, and a working social dashboard accessible at your Netlify URL.
Common use cases
Multi-Network Post Scheduler
Build a dashboard where you can compose a social post once and schedule it to publish on Twitter/X, LinkedIn, and a Facebook Page at a set date and time. The app stores scheduled posts in Supabase and uses cron-style background processing to publish them via each platform's API at the scheduled time.
Build a multi-network social media post scheduler. Create a Supabase table called scheduled_posts with columns: id, content (text), platforms (text array), scheduled_at (timestamp), status (pending/published/failed), created_at. Create API routes: POST /api/posts/schedule to save a scheduled post, POST /api/posts/publish/:id to immediately publish to selected platforms using credentials from env vars (TWITTER_BEARER_TOKEN, LINKEDIN_ACCESS_TOKEN, FACEBOOK_PAGE_TOKEN, FACEBOOK_PAGE_ID). Create a React dashboard with a compose form, platform checkboxes, a datetime picker, and a posts list showing status.
Copy this prompt to try it in Bolt.new
Social Media Analytics Dashboard
Pull follower counts, post engagement metrics, and reach data from Twitter/X, LinkedIn, and Facebook pages into a unified analytics dashboard. Display charts showing performance trends over time and highlight your top-performing posts.
Create a social media analytics dashboard. Build API routes that fetch data from: Twitter/X API v2 GET /2/users/:id/tweets with engagement metrics using TWITTER_BEARER_TOKEN, LinkedIn API analytics endpoint using LINKEDIN_ACCESS_TOKEN, and Facebook Graph API page insights using FACEBOOK_PAGE_TOKEN. Store fetched data in Supabase for historical tracking. Display a unified dashboard with line charts for follower growth, bar charts for post engagement, and a table of recent posts sorted by engagement rate. Use Recharts for visualizations.
Copy this prompt to try it in Bolt.new
Social Inbox and Mention Tracker
Monitor your brand mentions and comments across Twitter/X and Facebook in a single inbox interface. Fetch recent mentions on a schedule, display them in a unified feed, and allow your team to mark items as reviewed or responded.
Build a social media inbox. Create a Supabase table social_mentions with columns: id, platform, mention_id, author, content, url, is_reviewed, created_at. Create an API route POST /api/mentions/sync that fetches recent Twitter/X mentions using TWITTER_BEARER_TOKEN (GET /2/users/:id/mentions) and Facebook page comments using FACEBOOK_PAGE_TOKEN. Create a React inbox view showing mentions from all platforms in chronological order, with a 'Mark as reviewed' button for each item. Add a header with per-platform unread counts.
Copy this prompt to try it in Bolt.new
Troubleshooting
Twitter API returns 401 Unauthorized even though Bearer Token looks correct
Cause: Bearer Tokens are read-only and cannot post tweets. Publishing tweets requires OAuth 2.0 user context authorization with the tweet.write scope, using Client ID and Client Secret credentials — not a Bearer Token.
Solution: In the Twitter Developer Portal, create OAuth 2.0 credentials for your app and implement the OAuth 2.0 PKCE flow to get a user access token with tweet.write scope. Store the resulting access token (not the Bearer Token) for write operations. The Bearer Token is only for App-Only authentication (reading public data).
LinkedIn API returns 403 Forbidden when attempting to create UGC posts
Cause: The LinkedIn App does not have the required product approved. Creating posts requires the 'Share on LinkedIn' or 'Community Management API' product enabled on your LinkedIn Developer app, and the access token must be authorized with the w_member_social scope.
Solution: Go to your LinkedIn Developer app at developer.linkedin.com → Products tab, and request access to 'Share on LinkedIn'. This typically involves LinkedIn reviewing your use case. Ensure your OAuth access token was generated with the w_member_social scope — regenerate if needed. Organization posts additionally require the w_organization_social scope.
OAuth callback redirects fail — social platform says 'Redirect URI mismatch'
Cause: The redirect URI registered in the social platform's developer console does not match the URL being used for the OAuth callback. During Bolt.new development, the preview URL changes with every session, making it impossible to register as a stable OAuth redirect URI.
Solution: Deploy to Netlify first, then register your Netlify URL (e.g., https://your-app.netlify.app/api/auth/callback) as the OAuth redirect URI in each platform's developer console. Never test OAuth flows using the Bolt.new WebContainer preview URL. This is a core WebContainer limitation — the preview has no persistent public URL.
Facebook Graph API returns error 190: 'Error validating access token: Session has expired'
Cause: Facebook Page Access Tokens generated from the Graph API Explorer or via short-lived user tokens expire after 1-2 hours. Short-lived tokens are suitable for testing but not production use.
Solution: Exchange your short-lived Page Access Token for a long-lived token using the token exchange endpoint: GET https://graph.facebook.com/v18.0/oauth/access_token?grant_type=fb_exchange_token&client_id={app-id}&client_secret={app-secret}&fb_exchange_token={short-lived-token}. Long-lived Page Access Tokens (generated from long-lived user tokens) do not expire. Update FACEBOOK_PAGE_TOKEN in your environment variables with the long-lived token.
1// Fetch a long-lived page token2const response = await fetch(3 `https://graph.facebook.com/v18.0/oauth/access_token` +4 `?grant_type=fb_exchange_token` +5 `&client_id=${process.env.FACEBOOK_APP_ID}` +6 `&client_secret=${process.env.FACEBOOK_APP_SECRET}` +7 `&fb_exchange_token=${shortLivedToken}`8);9const { access_token } = await response.json();Best practices
- Use Promise.allSettled() instead of Promise.all() when publishing to multiple platforms — one platform failure should not block the others from receiving the post
- Cache social analytics data in Supabase and refresh on a schedule rather than fetching from platform APIs on every page load, since most platforms have strict daily rate limits
- Store all social API tokens in server-side environment variables only — never use NEXT_PUBLIC_ prefix for credentials that have write permissions
- Design your OAuth flow around your deployed Netlify or Bolt Cloud URL from day one — testing OAuth in the WebContainer preview is not possible due to unstable redirect URIs
- Always validate post content against each platform's character limits before calling the API — return platform-specific validation errors to the frontend so users can correct the content
- Implement token expiry detection and display clear error messages in the dashboard UI when a platform token expires, rather than silently failing
- Log all publish attempts with timestamps and results in Supabase — social media APIs can be flaky and having an audit trail is invaluable for debugging
- Use platform-specific preview rendering in your composer UI — a post may read well on LinkedIn but exceed Twitter's character limit
Alternatives
Buffer has a proper public API with clear documentation and generous free tier access, making it much more developer-friendly for Bolt integrations than Hootsuite's partner-gated API.
Sprout Social offers a full REST API but is geared toward enterprise teams managing complex social strategies — better than Hootsuite's API access but still requires enterprise pricing.
SocialBee focuses on content categorization and evergreen posting queues, offering a simpler API integration path for teams primarily concerned with content recycling and category-based scheduling.
Agorapulse provides a unified social inbox with a public API, making it a strong Hootsuite alternative for teams that need combined publishing and social listening in a single integration.
Frequently asked questions
Can I access Hootsuite's API from Bolt.new without partner access?
Hootsuite's full REST API requires completing their partner program application. Basic consumer API keys are not publicly available. For most Bolt developers, the practical path is connecting directly to the social platforms (Twitter/X, LinkedIn, Facebook) using their own developer APIs, which are freely accessible with a developer account.
Does Bolt.new's WebContainer support social media OAuth flows during development?
OAuth flows require a stable, registered redirect URI. Bolt.new's WebContainer preview uses a dynamic URL that changes per session and cannot be registered with social platform developer consoles. You need to deploy to Netlify or Bolt Cloud first, then register your deployed URL as the OAuth callback URI in each platform's developer settings.
Why do my social media API calls work in the Bolt preview but fail after deploying?
This is usually an environment variable issue. Variables in .env.local only exist during Bolt development — they are not deployed. After deploying to Netlify, add all environment variables in the Netlify dashboard under Site Configuration → Environment Variables, then trigger a redeploy for them to take effect.
Can I build a Hootsuite plugin or app extension in Bolt.new?
Yes, Hootsuite's App Directory SDK allows you to build iframe-based applications that run inside the Hootsuite interface. You would build a standard React web app in Bolt.new, deploy it to Netlify, and then register it as a Hootsuite app via the developer portal at developer.hootsuite.com. The Hootsuite SDK handles authentication and communication between your app and the Hootsuite container.
How do I handle social media platform rate limits in my Bolt app?
Implement caching as the primary defense — store API responses in Supabase and only refresh when needed. Display rate limit error states in your UI with retry-after information from the API response headers. For publishing, add client-side confirmation before bulk operations. Twitter/X's free tier is particularly restrictive, with write limits as low as 1,500 tweet operations per month per app.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation