Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Buffer

Buffer integrates with Bolt.new through its REST API using OAuth 2.0. Create a Buffer developer app, implement the OAuth flow in Next.js API routes (requires a deployed callback URL), then fetch social media profiles and schedule posts across Twitter, Facebook, LinkedIn, and Instagram. Buffer's API is clean and well-documented with a free developer tier for 3 channels.

What you'll learn

  • How to register a Buffer developer application and set up OAuth 2.0 credentials for your Bolt.new integration
  • How to implement Buffer's OAuth authorization code flow with a Next.js callback route and token storage
  • How to fetch connected social media profiles from Buffer and display them in a React dashboard
  • How to create and schedule social media posts to specific Buffer profiles via the API
  • How to build a social media scheduling queue dashboard showing pending and sent posts from Buffer
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate20 min read20 minutesMarketingApril 2026RapidDev Engineering Team
TL;DR

Buffer integrates with Bolt.new through its REST API using OAuth 2.0. Create a Buffer developer app, implement the OAuth flow in Next.js API routes (requires a deployed callback URL), then fetch social media profiles and schedule posts across Twitter, Facebook, LinkedIn, and Instagram. Buffer's API is clean and well-documented with a free developer tier for 3 channels.

Building a Buffer Social Scheduling Integration in Bolt.new

Buffer is one of the most developer-friendly social media tools available. Its REST API follows clean conventions, the documentation is thorough (hosted on GitHub at bufferapp/api-reference), and the free plan includes 3 social channels — enough for building and testing an integration without any cost. For Bolt.new developers building social media management tools, content calendars, or marketing automation apps, Buffer provides the scheduling backbone that pushes content to Twitter/X, Facebook Pages, LinkedIn, Instagram, and Pinterest.

The core Buffer workflow from an API perspective is: (1) the user connects their social accounts to Buffer (this happens in Buffer's dashboard, not through your app), (2) your app authenticates with Buffer via OAuth to get access to those connected profiles, (3) your app calls the Buffer API to fetch the user's profiles, create new posts in the queue, and read analytics on sent posts. Buffer handles the actual posting to each social network at the scheduled time — your app does not need direct Twitter or Instagram API access.

This architecture makes Buffer particularly appealing for social media management apps: instead of managing five different social network API integrations (Twitter, Facebook, LinkedIn, Instagram, Pinterest) with their individual authentication flows, rate limits, and data formats, you integrate once with Buffer and get a unified API for all of them. Buffer normalizes the differences between platforms and presents a consistent JSON interface for scheduling and analytics.

The note about 'paypal authorization buffer' in the search queries is a topic mismatch — PayPal authorization and Buffer social scheduling are entirely unrelated. This guide covers Buffer's social media API integration only.

Integration method

Bolt Chat + API Route

Buffer integrates with Bolt.new through its REST API using OAuth 2.0 authorization. Register a developer app at buffer.com/developers, implement the OAuth code flow through Next.js API routes (the callback URL requires a deployed app since Bolt's WebContainer has no stable URL), and use the resulting access token to manage social profiles and schedule posts. Buffer's API is among the cleanest in the social media tool space — well-documented with consistent REST conventions, a free developer plan for 3 social channels, and no webhook requirement for the core scheduling workflow.

Prerequisites

  • A Buffer account at buffer.com — the free Essentials plan (up to 3 channels) is sufficient for development and testing
  • A Buffer developer application registered at buffer.com/developers/apps (or api.bufferapp.com/1/oauth2/register) — you need the Client ID and Client Secret
  • At least one social media account connected to Buffer (Twitter/X, LinkedIn, or Facebook Page) to have profiles available for API testing
  • A deployed Bolt.new app URL on Netlify or Bolt Cloud for the OAuth callback — Buffer's OAuth requires a registered redirect URI and the WebContainer's dynamic URL cannot be registered
  • A Next.js project in Bolt.new for server-side API routes — Buffer OAuth tokens must be kept server-side to prevent unauthorized access to the user's social media accounts

Step-by-step guide

1

Register a Buffer developer application and configure OAuth

Buffer's developer portal is at buffer.com/developers. The application registration process is straightforward compared to many social media APIs — no review process, instant credential generation, and a free developer account. Log into your Buffer account, then go to buffer.com/developers/apps/create (or navigate via the developer portal). Fill in the application name (visible to users during OAuth authorization), website URL, and description. The most important field is the OAuth Callback URL — enter your deployed app's callback URL in the format https://yourapp.netlify.app/api/auth/buffer/callback. You can only have one callback URL per app; for multiple environments, either create separate apps (one for development, one for production) or use a single deployed URL for all testing. After creating the application, Buffer shows you the Client ID (called 'client_id') and Client Secret ('client_secret'). Add these as BUFFER_CLIENT_ID and BUFFER_CLIENT_SECRET to your Bolt project's .env file. Also add BUFFER_REDIRECT_URI set to your callback URL. Buffer's OAuth 2.0 implementation uses the standard authorization code flow with one notable difference: Buffer's authorization endpoint URL is https://bufferapp.com/oauth2/authorize (not api.bufferapp.com). The token exchange endpoint is https://api.bufferapp.com/1/oauth2/token.php. Note the .php extension — this is a legacy detail of Buffer's API implementation. Scopes: Buffer's API uses a single scope model — there are no granular read/write scopes. Authorization grants full access to the user's Buffer account (profiles, updates, analytics). This means your app can read and write posts for all connected social profiles once the user authorizes. Communicate this clearly in your app's authorization prompt. Buffer access tokens do not expire — they remain valid until the user revokes access from their Buffer account settings. This simplifies token management significantly compared to APIs that require token refresh.

Bolt.new Prompt

Set up Buffer OAuth configuration. Add BUFFER_CLIENT_ID, BUFFER_CLIENT_SECRET, and BUFFER_REDIRECT_URI to .env with placeholder values. Create lib/buffer/config.ts exporting: BUFFER_AUTH_URL ('https://bufferapp.com/oauth2/authorize'), BUFFER_TOKEN_URL ('https://api.bufferapp.com/1/oauth2/token.php'), BUFFER_API_BASE ('https://api.bufferapp.com/1'), buildAuthUrl(state: string) function that constructs the authorization URL with client_id, redirect_uri, response_type=code, and state, getBufferHeaders(token: string) function returning Authorization Bearer header. Export TypeScript interfaces for BufferProfile (id, service, service_username, avatar_https) and BufferUpdate (id, text, scheduled_at, status, statistics).

Paste this in Bolt.new chat

lib/buffer/config.ts
1// lib/buffer/config.ts
2export const BUFFER_AUTH_URL = 'https://bufferapp.com/oauth2/authorize';
3export const BUFFER_TOKEN_URL = 'https://api.bufferapp.com/1/oauth2/token.php';
4export const BUFFER_API_BASE = 'https://api.bufferapp.com/1';
5
6export function buildAuthUrl(state: string): string {
7 const params = new URLSearchParams({
8 client_id: process.env.BUFFER_CLIENT_ID ?? '',
9 redirect_uri: process.env.BUFFER_REDIRECT_URI ?? '',
10 response_type: 'code',
11 state,
12 });
13 return `${BUFFER_AUTH_URL}?${params.toString()}`;
14}
15
16export function getBufferHeaders(token: string): Record<string, string> {
17 return {
18 Authorization: `Bearer ${token}`,
19 'Content-Type': 'application/x-www-form-urlencoded',
20 };
21}
22
23export interface BufferProfile {
24 id: string;
25 service: 'twitter' | 'facebook' | 'linkedin' | 'instagram' | 'pinterest' | 'tiktok';
26 service_username: string;
27 service_id: string;
28 avatar_https: string;
29 formatted_service: string;
30 default: boolean;
31 paused: boolean;
32 disconnected: boolean;
33}
34
35export interface BufferUpdateStatistics {
36 reach?: number;
37 clicks?: number;
38 retweets?: number;
39 likes?: number;
40 comments?: number;
41}
42
43export interface BufferUpdate {
44 id: string;
45 created_at: number; // Unix timestamp
46 day: string;
47 due_time: string;
48 due_at: number;
49 profile_id: string;
50 profile_service: string;
51 status: 'buffer' | 'sent' | 'service_update_sent';
52 text: string;
53 text_formatted?: string;
54 statistics?: BufferUpdateStatistics;
55}

Pro tip: Buffer's token endpoint URL includes a .php extension (token.php) — this is correct and not a typo. Buffer's API was built in the early PHP era and retains this URL for backward compatibility. Always use the exact URL as documented.

Expected result: The Buffer configuration module exports the OAuth URL builder, header generator, and TypeScript interfaces. The .env file has placeholder variables for Client ID, Client Secret, and Redirect URI. The getBufferHeaders function adds the Bearer token automatically to all API calls.

2

Implement Buffer OAuth 2.0 authorization flow

Buffer's OAuth flow follows the standard authorization code pattern. The user clicks 'Connect Buffer,' is redirected to Buffer's authorization page, approves your app, and is redirected back to your callback URL with an authorization code. You exchange this code for a permanent access token. The authorization initiation route (GET /api/auth/buffer) generates a random state string for CSRF protection, stores it in a cookie, and redirects to Buffer's OAuth authorization page. Use the buildAuthUrl function from your config module to construct the full URL with client_id, redirect_uri, response_type=code, and state. The callback route (GET /api/auth/buffer/callback) handles the return from Buffer. It validates the state cookie, extracts the authorization code from query params, and POSTs to Buffer's token endpoint to exchange the code for an access token. Buffer's token endpoint accepts application/x-www-form-urlencoded data (not JSON) with fields: client_id, client_secret, redirect_uri, code, and grant_type=authorization_code. Buffer returns a JSON response with an access_token field and a token_type of 'bearer'. There is no refresh_token (Buffer tokens are permanent) and no expires_in. Store the access token in an HTTP-only cookie or your Supabase database. After getting the token, verify it works by calling GET /1/user.json — this returns the Buffer user object with the user's name, email, and account details. If this call succeeds, the token is valid and the OAuth flow is complete. Set the access token in a secure HTTP-only cookie and redirect to the dashboard. Error handling: Buffer redirects to your callback with an error parameter if the user denies access or if something goes wrong. Check for the error query param in your callback route and handle it with an appropriate redirect to your auth error page. As with all OAuth flows in Bolt.new: the callback URL must be publicly accessible. Deploy to Netlify or Bolt Cloud before testing the full OAuth flow. For development, you can generate a personal access token through Buffer's API and store it directly in BUFFER_ACCESS_TOKEN to skip OAuth during development.

Bolt.new Prompt

Implement Buffer OAuth 2.0 flow. Create two Next.js API routes: (1) app/api/auth/buffer/route.ts: generate a random state with crypto.randomUUID(), save to 'buffer_oauth_state' cookie, and redirect to buildAuthUrl(state) from lib/buffer/config.ts. (2) app/api/auth/buffer/callback/route.ts: check for error query param (redirect to /auth/error if present), validate state cookie, extract code, POST to BUFFER_TOKEN_URL with application/x-www-form-urlencoded body including client_id, client_secret, redirect_uri, code, and grant_type=authorization_code, parse access_token from response, set it in an httpOnly cookie 'buffer_token' (1 year maxAge), and redirect to /dashboard. Handle all errors with appropriate redirects.

Paste this in Bolt.new chat

app/api/auth/buffer/callback/route.ts
1// app/api/auth/buffer/callback/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { BUFFER_TOKEN_URL } from '@/lib/buffer/config';
4
5export async function GET(request: NextRequest) {
6 const { searchParams } = new URL(request.url);
7 const code = searchParams.get('code');
8 const state = searchParams.get('state');
9 const error = searchParams.get('error');
10 const stateCookie = request.cookies.get('buffer_oauth_state')?.value;
11
12 // Handle user denying access
13 if (error) {
14 return NextResponse.redirect(
15 new URL(`/auth/error?reason=${encodeURIComponent(error)}`, request.url)
16 );
17 }
18
19 // CSRF validation
20 if (!state || state !== stateCookie) {
21 return NextResponse.redirect(new URL('/auth/error?reason=invalid_state', request.url));
22 }
23 if (!code) {
24 return NextResponse.redirect(new URL('/auth/error?reason=no_code', request.url));
25 }
26
27 try {
28 // Exchange authorization code for access token
29 const tokenResponse = await fetch(BUFFER_TOKEN_URL, {
30 method: 'POST',
31 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
32 body: new URLSearchParams({
33 client_id: process.env.BUFFER_CLIENT_ID ?? '',
34 client_secret: process.env.BUFFER_CLIENT_SECRET ?? '',
35 redirect_uri: process.env.BUFFER_REDIRECT_URI ?? '',
36 code,
37 grant_type: 'authorization_code',
38 }),
39 });
40
41 if (!tokenResponse.ok) {
42 const errorText = await tokenResponse.text();
43 console.error('Buffer token exchange failed:', errorText);
44 return NextResponse.redirect(new URL('/auth/error?reason=token_exchange', request.url));
45 }
46
47 const tokenData = await tokenResponse.json() as { access_token: string; token_type: string };
48 const accessToken = tokenData.access_token;
49
50 if (!accessToken) {
51 return NextResponse.redirect(new URL('/auth/error?reason=no_token', request.url));
52 }
53
54 // Set token in secure HTTP-only cookie
55 const response = NextResponse.redirect(new URL('/dashboard', request.url));
56 response.cookies.set('buffer_token', accessToken, {
57 httpOnly: true,
58 secure: process.env.NODE_ENV === 'production',
59 maxAge: 60 * 60 * 24 * 365, // Buffer tokens don't expire — 1 year for cookie
60 sameSite: 'lax',
61 path: '/',
62 });
63 response.cookies.delete('buffer_oauth_state');
64
65 return response;
66 } catch (err) {
67 console.error('Buffer OAuth callback error:', err);
68 return NextResponse.redirect(new URL('/auth/error?reason=server_error', request.url));
69 }
70}

Pro tip: Buffer does not provide refresh tokens since access tokens are permanent. If a user's Buffer token stops working (because they revoked access in Buffer's settings), handle the 401 response by clearing the token cookie and redirecting to the authorization flow. Do not implement token refresh logic — it is not needed for Buffer.

Expected result: After deploying and registering the redirect URI at buffer.com/developers/apps, clicking 'Connect Buffer' redirects to Buffer's OAuth page. After authorizing, the user is redirected to /dashboard with the Buffer access token stored in an HTTP-only cookie. Subsequent API calls work using this token.

3

Fetch Buffer profiles and display the connected social accounts

After OAuth authorization, the first useful API call is fetching the user's connected social media profiles. Buffer profiles represent each connected social account — a Twitter account, a LinkedIn page, a Facebook Page, etc. These profiles are the target for creating new posts. The profiles endpoint is GET /1/profiles.json. With the Bearer token in the Authorization header, it returns an array of profile objects. Each profile has: id (the Buffer profile ID used in all subsequent calls), service (the social network name: 'twitter', 'facebook', 'linkedin', 'instagram', 'pinterest', 'tiktok'), service_username (the username on that network), service_id (the native ID on that network), avatar_https (profile picture URL), formatted_service (display name like 'Twitter'), and paused/disconnected booleans. For the profiles selector UI, display profiles as a grid of cards or a select dropdown. Show the social network name and icon (using the service field to pick an icon), the username or page name, and the profile picture avatar. Use distinct colors per network: Twitter/X in black, LinkedIn in blue, Facebook in dark blue, Instagram in gradient, etc. These visual cues help users quickly identify which accounts they are scheduling to. Filter out disconnected and paused profiles in your API route response. Disconnected profiles are social accounts that have been removed from Buffer or whose tokens have expired on the network side — they cannot receive new posts. Paused profiles are temporary pauses the user set — they can be resumed but posts won't publish while paused. For multi-profile post scheduling (posting the same content to multiple networks simultaneously), collect all selected profile IDs in an array. Buffer's create update endpoint accepts profile_ids as an array, allowing one API call to schedule a post across multiple profiles. Store the fetched profiles in React state or SWR/React Query cache with a 5-minute revalidation. Profiles rarely change during a session, so re-fetching on every render is unnecessary.

Bolt.new Prompt

Build a Buffer profiles API route and display component. Create app/api/buffer/profiles/route.ts that reads 'buffer_token' cookie, calls GET https://api.bufferapp.com/1/profiles.json with Bearer token header, filters out disconnected profiles, and returns profile id, service, service_username, avatar_https, formatted_service, and paused. Build a ProfileSelector client component that: (1) Fetches profiles from the route on mount, (2) Displays each as a card with service icon emoji (🐦 Twitter, 💼 LinkedIn, 📘 Facebook, 📷 Instagram), username, and avatar, (3) Supports multi-select (click to toggle selected state with visual highlight), (4) Returns selected profile IDs via an onChange prop. Show a 'Connect Buffer' link if 401 is returned. Use Tailwind CSS.

Paste this in Bolt.new chat

app/api/buffer/profiles/route.ts
1// app/api/buffer/profiles/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { BUFFER_API_BASE, getBufferHeaders, type BufferProfile } from '@/lib/buffer/config';
4
5export async function GET(request: NextRequest) {
6 const token = request.cookies.get('buffer_token')?.value;
7
8 if (!token) {
9 return NextResponse.json({ error: 'Not authenticated with Buffer' }, { status: 401 });
10 }
11
12 try {
13 const response = await fetch(`${BUFFER_API_BASE}/profiles.json`, {
14 headers: getBufferHeaders(token),
15 });
16
17 if (response.status === 401) {
18 return NextResponse.json(
19 { error: 'Buffer token invalid or revoked — re-authorize' },
20 { status: 401 }
21 );
22 }
23
24 if (!response.ok) {
25 return NextResponse.json(
26 { error: `Buffer API error: ${response.status}` },
27 { status: response.status }
28 );
29 }
30
31 const profiles: BufferProfile[] = await response.json();
32
33 // Filter usable profiles
34 const usable = profiles
35 .filter((p) => !p.disconnected)
36 .map((p) => ({
37 id: p.id,
38 service: p.service,
39 username: p.service_username,
40 avatar: p.avatar_https,
41 formattedService: p.formatted_service,
42 paused: p.paused,
43 }));
44
45 return NextResponse.json({ profiles: usable });
46 } catch (error) {
47 const message = error instanceof Error ? error.message : 'Request failed';
48 return NextResponse.json({ error: message }, { status: 500 });
49 }
50}

Pro tip: Buffer profile IDs are stable — they do not change when the user reconnects their social account to Buffer. You can safely cache profile IDs in your database for linking Buffer profiles to other objects in your app (campaign records, content items, etc.).

Expected result: The profiles API route returns the user's connected social accounts filtered to active (non-disconnected) profiles. The ProfileSelector component displays each account as a clickable card with service icon and username. Selected profiles are highlighted, and the component returns the array of selected IDs.

4

Create and schedule social media posts via Buffer API

Creating a post in Buffer's queue is a POST to /1/updates/create.json. This is the core functionality that makes Buffer integrations valuable — from any form in your Bolt app, you can add content to the social media scheduling queue without leaving your product. The create update request requires two fields: profile_ids (array of Buffer profile ID strings) and text (the post content). Optional fields include: media (object with link, thumbnail, picture, photo URLs for link previews and image attachments), scheduled_at (Unix timestamp for specific scheduling time — if omitted, Buffer places the post in the optimal queue slot), now (boolean, if true posts immediately), retweet (for Twitter retweets), and attachment (boolean for link attachment metadata generation). Buffer returns a response with updates array — one update object per profile_id you specified. Each update has the Buffer update ID, the profile_id it was scheduled for, status ('buffer' for queued, or 'sent' if posted immediately), and the scheduled due_at timestamp. Character limits vary by social network: Twitter/X is 280 characters, LinkedIn allows 3,000 for personal profiles and 700 for company pages, Facebook allows much longer posts. Buffer does not enforce these limits in the API — exceeding a network's limit results in the post failing to publish (Buffer will mark it as failed in the queue). Add client-side character counting to your post creation form. Media attachments for image posts require an accessible image URL. Buffer does not accept binary file uploads — the image must be hosted at a public URL. For apps where users upload images, first upload to Supabase Storage (which provides public URLs) or another CDN, then pass the public URL to Buffer's media.picture field. Link preview generation is automatic when you include a URL in the text field — Buffer detects it, fetches the open graph metadata, and creates a link preview card. Set attachment: false in the create request if you want the URL to appear as plain text without a preview card.

Bolt.new Prompt

Build a social media post creation form with Buffer integration. Create an API route at app/api/buffer/posts/route.ts that accepts POST with { profileIds, text, scheduledAt } and calls POST https://api.bufferapp.com/1/updates/create.json with form-encoded body: profile_ids[] (array), text, and optionally scheduled_at (Unix timestamp). Return the created Buffer update IDs and scheduled times. Build a CreatePostForm client component with: profile selector using ProfileSelector, post text textarea with character counter (shows count and turns red over 280 chars for Twitter), optional datetime picker for specific scheduling (label: 'Schedule for specific time — leave empty for optimal Buffer timing'), and submit button. Use react-hook-form and zod validation (text min 1, profileIds min 1).

Paste this in Bolt.new chat

app/api/buffer/posts/route.ts
1// app/api/buffer/posts/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { BUFFER_API_BASE, getBufferHeaders } from '@/lib/buffer/config';
4
5interface CreatePostBody {
6 profileIds: string[];
7 text: string;
8 scheduledAt?: number; // Unix timestamp in seconds
9 mediaUrl?: string;
10 mediaThumbnailUrl?: string;
11 postNow?: boolean;
12}
13
14export async function POST(request: NextRequest) {
15 const token = request.cookies.get('buffer_token')?.value;
16
17 if (!token) {
18 return NextResponse.json({ error: 'Not authenticated with Buffer' }, { status: 401 });
19 }
20
21 const body: CreatePostBody = await request.json();
22
23 if (!body.text?.trim()) {
24 return NextResponse.json({ error: 'Post text is required' }, { status: 400 });
25 }
26 if (!body.profileIds?.length) {
27 return NextResponse.json({ error: 'At least one profile must be selected' }, { status: 400 });
28 }
29
30 try {
31 // Buffer create update uses form-encoded data
32 const formData = new URLSearchParams();
33 formData.append('text', body.text);
34 body.profileIds.forEach((id) => formData.append('profile_ids[]', id));
35
36 if (body.postNow) {
37 formData.append('now', 'true');
38 } else if (body.scheduledAt) {
39 formData.append('scheduled_at', String(body.scheduledAt));
40 }
41
42 if (body.mediaUrl) {
43 formData.append('media[picture]', body.mediaUrl);
44 }
45 if (body.mediaThumbnailUrl) {
46 formData.append('media[thumbnail]', body.mediaThumbnailUrl);
47 }
48
49 const response = await fetch(`${BUFFER_API_BASE}/updates/create.json`, {
50 method: 'POST',
51 headers: getBufferHeaders(token),
52 body: formData,
53 });
54
55 if (!response.ok) {
56 const error = await response.json().catch(() => ({ error: response.statusText }));
57 return NextResponse.json(
58 { error: error.error ?? `Buffer API error: ${response.status}` },
59 { status: response.status }
60 );
61 }
62
63 const result = await response.json();
64 const updates = result.updates ?? [];
65
66 return NextResponse.json({
67 success: true,
68 updates: updates.map((u: { id: string; profile_service: string; due_at: number; status: string }) => ({
69 id: u.id,
70 service: u.profile_service,
71 scheduledAt: u.due_at,
72 status: u.status,
73 })),
74 }, { status: 201 });
75 } catch (error) {
76 const message = error instanceof Error ? error.message : 'Failed to create post';
77 return NextResponse.json({ error: message }, { status: 500 });
78 }
79}

Pro tip: Buffer's create update API uses application/x-www-form-urlencoded (not JSON) for the request body. Multi-value fields like profile_ids must be sent as repeated form fields with the array bracket syntax: profile_ids[]=id1&profile_ids[]=id2. Use URLSearchParams.append() to add multiple values with the same key as shown in the code.

Expected result: Submitting the post creation form schedules the post in Buffer's queue for all selected profiles. The API returns the update IDs and scheduled times. The post appears in the Buffer dashboard for each connected social account, ready to publish at the scheduled time.

Common use cases

Social Media Scheduling Queue Dashboard

A custom dashboard that shows the user's Buffer posting queue across all their connected social profiles. Marketers can see all scheduled posts in one view, edit pending posts before they are published, and reschedule or reorder items in the queue. Uses Buffer's API to fetch profiles and their pending updates.

Bolt.new Prompt

Build a Buffer social scheduling dashboard. Create Next.js API routes: (1) /api/buffer/profiles that calls GET https://api.bufferapp.com/1/profiles.json with the Buffer OAuth token from cookie and returns profile id, service (twitter/facebook/linkedin), service_username, and avatar, (2) /api/buffer/updates/[profileId]/pending that fetches GET /1/profiles/{profileId}/updates/pending.json and returns the scheduled posts. Build a dashboard with a profile selector dropdown, then show pending posts as a list with post text, scheduled time, and 'Edit' and 'Delete' buttons. Use Tailwind CSS and shadcn/ui.

Copy this prompt to try it in Bolt.new

Content Calendar with Buffer Auto-Scheduling

A content planning calendar where users write and tag posts by topic, then schedule them to the optimal times in Buffer's queue. The app calls Buffer's create update API to add posts to the schedule, taking advantage of Buffer's 'Optimal Timing' feature which automatically picks the best posting times based on the audience's engagement patterns.

Bolt.new Prompt

Build a content calendar that creates Buffer posts. Build a form with: post text textarea, profile selector (multi-select checkboxes fetched from /api/buffer/profiles), scheduled_at datetime picker (optional — leave empty to use Buffer's optimal timing), and a media URL field. Create a server action that POSTs to https://api.bufferapp.com/1/updates/create.json with the Buffer token, profile_ids array, text, and optionally scheduled_at (or now=true for immediate posting). Show a success toast with the Buffer update IDs after creating. Use react-hook-form and zod for validation.

Copy this prompt to try it in Bolt.new

Post Analytics Dashboard from Buffer

An analytics view showing published post performance data from Buffer — impressions, engagements, clicks, and reach for posts sent in the last 30 days. Groups analytics by social network and shows top-performing posts. Uses Buffer's sent updates and analytics endpoints.

Bolt.new Prompt

Build a Buffer analytics dashboard. Create an API route at /api/buffer/analytics/[profileId] that fetches GET https://api.bufferapp.com/1/profiles/{profileId}/updates/sent.json?count=30 with the Buffer Bearer token and returns sent posts with statistics (clicks, reach, likes, comments, shares, date). Build an analytics page that: (1) Shows a profile selector at the top, (2) Displays summary cards for total posts, total engagements, and average engagement per post, (3) Lists the top 5 posts by engagement with post text and engagement breakdown, (4) Shows an EngagementChart using Recharts BarChart with days on X-axis and total engagements on Y-axis. Use Tailwind CSS.

Copy this prompt to try it in Bolt.new

Troubleshooting

Buffer API returns 401 Unauthorized on profile or post creation requests

Cause: The Buffer access token in the cookie has been revoked by the user in Buffer's account settings (Settings → Apps → Revoke access), the cookie has expired, or the token was never properly set after OAuth completion.

Solution: Check if the buffer_token cookie exists in the browser DevTools (Application → Cookies). If missing, the user needs to re-authorize via /api/auth/buffer. If present, test the token by calling GET /1/user.json manually from a Next.js API route. If that returns 401, the token has been revoked and the user must re-authorize. Handle 401 responses in all API routes by returning a 401 JSON error, which the frontend should detect and redirect to the auth flow.

typescript
1// In API routes: detect 401 and return actionable error
2if (response.status === 401) {
3 return NextResponse.json(
4 { error: 'Buffer access revoked — reconnect at /api/auth/buffer', requiresReauth: true },
5 { status: 401 }
6 );
7}
8// In React component: detect requiresReauth and redirect
9if (data.requiresReauth) {
10 window.location.href = '/api/auth/buffer';
11}

Buffer post creation returns error 'No profiles were provided' even though profile IDs are being sent

Cause: The profile_ids array is not being sent in the correct form-encoded format. Buffer expects repeated form fields with bracket syntax (profile_ids[]=id1&profile_ids[]=id2), not a JSON array in the body.

Solution: Ensure the request body is URLSearchParams (form-encoded), not JSON. Use URLSearchParams.append('profile_ids[]', id) for each profile ID rather than setting a single profile_ids field with an array value. Also verify the Content-Type header is 'application/x-www-form-urlencoded' — Buffer ignores JSON bodies for this endpoint.

typescript
1// WRONG: JSON body
2const body = JSON.stringify({ profile_ids: ['id1', 'id2'], text: 'Post' });
3
4// CORRECT: Form-encoded with array bracket notation
5const formData = new URLSearchParams();
6formData.append('text', postText);
7profileIds.forEach((id) => formData.append('profile_ids[]', id)); // Note: []
8await fetch(`${BUFFER_API_BASE}/updates/create.json`, {
9 method: 'POST',
10 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded' },
11 body: formData,
12});

Buffer OAuth callback fails with 'Invalid redirect_uri' even though the URL looks correct

Cause: The redirect_uri in the authorization request does not exactly match the registered callback URL in Buffer's developer application settings — includes/excludes trailing slash, HTTP vs HTTPS mismatch, or subdomain difference.

Solution: Log in to buffer.com/developers/apps, edit your application, and copy the exact Redirect URI you registered. Update BUFFER_REDIRECT_URI in your .env to match character-for-character including protocol (https://), subdomain, path, and any trailing slash or lack thereof. Also check the deployed URL — if you recently redeployed to a different Netlify subdomain, update both the .env and the Buffer app registration.

typescript
1// Debug: log the exact URI being sent to Buffer
2const authUrl = buildAuthUrl(state);
3console.log('Auth URL params:', new URL(authUrl).searchParams.toString());
4// Verify redirect_uri param matches exactly what is in Buffer developer settings

Best practices

  • Buffer access tokens never expire but can be revoked — always handle 401 responses gracefully by detecting the requiresReauth scenario and prompting users to reconnect rather than showing a generic error
  • Use Buffer's optimal timing feature (omit scheduled_at when creating posts) for users who do not need specific scheduling — Buffer's algorithm picks posting times based on historical engagement data for each profile
  • Validate post character counts client-side before submitting — Twitter/X has a 280-character limit, LinkedIn 3,000, and exceeding limits causes Buffer to fail to publish the post without a clear error to the user
  • Send images as publicly accessible URLs rather than file uploads — upload images to Supabase Storage first to get a public URL, then include it in the Buffer media.picture field
  • Rate limit your API calls to Buffer — the API allows reasonable rates for normal use but does not publish exact limits; implement exponential backoff for 429 responses and avoid bulk operations without delays
  • For post deletion, use DELETE /1/updates/{id}/destroy.json — if a user edits a post in your UI, delete the old Buffer update and create a new one rather than using the update endpoint, as post editing has more restrictive timing requirements
  • Test the complete OAuth flow on your deployed app before considering the integration ready — the OAuth callback cannot work in Bolt's WebContainer preview, and untested OAuth flows commonly have redirect URI issues that only appear in production

Alternatives

Frequently asked questions

Can I post to Twitter, LinkedIn, and Instagram from one Buffer API call in Bolt.new?

Yes — Buffer's create update API accepts an array of profile_ids, so you can schedule the same content to multiple social networks in a single API call. Include all target profile IDs in the profile_ids[] form fields. Buffer will create one update per profile, each appearing in the respective network's queue. Note that optimal post text differs by network (Twitter has 280 chars, LinkedIn allows longer), so consider creating separate API calls with network-specific content for best results.

Does Buffer integration work in Bolt.new's WebContainer preview?

The API calls (fetching profiles, creating posts, reading analytics) work in the Bolt WebContainer since they are outbound HTTPS requests from Next.js API routes. However, the OAuth authorization flow requires a stable public HTTPS callback URL, which the WebContainer cannot provide. Deploy to Netlify or Bolt Cloud first to complete the OAuth flow and get a Buffer access token. For development without OAuth, generate a personal access token in Buffer settings and store it directly in BUFFER_ACCESS_TOKEN for testing API functionality in the preview.

How do I get a Buffer personal access token for development?

Buffer provides personal access tokens for development at buffer.com/developers/api. Log in to your Buffer account, go to the developer settings, and you can generate a personal access token that grants access to your own Buffer account without going through the OAuth flow. Store this as BUFFER_ACCESS_TOKEN in your .env for development. When building for production multi-user apps, implement the full OAuth flow so each user authorizes your app with their own Buffer account.

Does Buffer support posting images and videos to social media?

Yes — include a media.picture URL (publicly accessible image URL) in the create update request for image posts. Buffer supports images for Twitter, Facebook, LinkedIn, and Instagram. Video posting requires the media.video or uploading directly through Buffer's dashboard first and using the asset ID. For image posts from Bolt.new apps, upload images to Supabase Storage to get a public URL, then pass that URL to Buffer's media.picture field.

Do Buffer access tokens expire?

No — Buffer access tokens are permanent. They remain valid until the user manually revokes your app's access in Buffer's account settings (Settings → Integrations → Revoke). Unlike OAuth APIs that use short-lived access tokens requiring refresh, Buffer tokens last indefinitely. Handle 401 responses by detecting revoked access and prompting re-authorization rather than implementing refresh token logic.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.