# Sprout Social

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 2–3 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Sprout Social using the API Connector with a Bearer token (OAuth 2.0 access token from an Advanced plan account) at https://api.sproutsocial.com/v1. The primary use case is custom analytics dashboards: Sprout's analytics fields vary by network (Facebook, Twitter, LinkedIn each return different field names), and data has a 24-72 hour lag. API access requires an Advanced Sprout Social plan — Standard and Professional accounts receive 403 errors regardless of token validity.

## Building Sprout Social Analytics Dashboards in Bubble: The Three Technical Realities

Sprout Social is not primarily a scheduling tool — it is a social analytics platform. The Bubble integration exists to surface that analytics data in a custom reporting interface: showing engagement rates, impressions, click-through metrics, and audience growth for social teams who work within a Bubble app rather than jumping between tools.

Three technical realities define the integration:

**Reality 1: Plan gating is absolute.** Sprout Social's API is exclusively available on Advanced plan accounts. Standard and Professional plan accounts return 403 Forbidden on every API call, regardless of whether the OAuth token is correctly configured. Before building anything, confirm with your Sprout account manager that your subscription includes API access. There is no trial access or sandbox environment.

**Reality 2: Analytics data lags.** Sprout Social's analytics endpoints do not show real-time metrics. Twitter data is available with approximately 24 hours of delay. Facebook and Instagram data lags 48-72 hours. LinkedIn data has similar delays. Any dashboard you build must include a freshness disclaimer — showing 'as of [yesterday/2 days ago]' is not a limitation you added, it is a constraint of how social platforms deliver analytics data to third parties.

**Reality 3: Field names vary by network.** A Facebook post uses the field name `post_impressions` for impressions. A Twitter post uses `impressions`. A LinkedIn post also uses `impressions` but calculates it differently (LinkedIn counts unique viewers, Twitter counts total impressions including repeated views). Building a Bubble dashboard that handles all three networks requires defensive field access — checking whether a field exists before displaying it, rather than assuming all networks return the same schema.

Despite these complexities, the payoff is a custom reporting dashboard that shows exactly the social metrics your team cares about, in a Bubble interface tailored to your brand and workflow — without requiring every team member to have Sprout Social login credentials.

## Before you start

- A Sprout Social Advanced plan account — Standard and Professional plans do not include API access
- A Sprout Social Bearer token generated from Sprout developer settings or obtained via the partner portal — confirm API access eligibility with your account manager first
- A Bubble account on any plan (the API Connector and Toolbox plugin both work on the free plan for analytics dashboards; Backend Workflows for inbound events require a paid plan)
- The Toolbox plugin by Zeroqode installed in your Bubble app — needed for ISO 8601 date conversion via JavaScript
- At least one social profile connected to your Sprout Social account

## Step-by-step guide

### 1. Confirm API Access and Generate Your Sprout Social Bearer Token

Before writing a single Bubble workflow, confirm that your Sprout Social account includes API access. Contact your Sprout Social account manager or check your plan details at sproutsocial.com/pricing. Only Advanced plan subscribers (and above) have API access — building the integration without confirming this will result in 403 errors on every call regardless of implementation correctness.

Once API access is confirmed, log in to your Sprout Social account and navigate to the developer settings or partner portal (the exact location depends on your Sprout account configuration — your account manager can direct you to the correct page). Generate a Bearer token (OAuth 2.0 access token) with the permissions needed for your use case: read access for analytics and profile metadata, write access if you plan to create or schedule posts.

Copy the generated token immediately — it may only be shown once. Store it in a password manager or a secure notes file. This token authorizes API calls against your Sprout Social account and should be treated as a credential.

Also note any scope limitations on the token. Listening and mentions endpoints may require higher plan tiers than core scheduling and analytics — verify per your Sprout plan before building those features.

**Expected result:** You have confirmed API access on your Sprout Social plan and have a Bearer token ready to paste into Bubble's API Connector. You know which API endpoints and scopes are available to your account.

### 2. Install the API Connector and Toolbox Plugin; Configure Sprout Authentication

In your Bubble editor, go to Plugins → Add plugins. Search for and install:
1. 'API Connector' by Bubble (free) — for Sprout API calls
2. 'Toolbox' by Zeroqode (free) — for JavaScript execution needed to convert Bubble dates to ISO 8601 format

In the API Connector, click 'Add another API'. Name it 'Sprout Social'. Base URL: https://api.sproutsocial.com/v1

Add a shared header:
- Name: Authorization
- Value: Bearer [paste your token here]
- Check the 'Private' checkbox — this keeps the token server-side in Bubble's proxy

Add a second shared header:
- Name: Content-Type
- Value: application/json

This Content-Type header is needed for POST calls (scheduling messages). GET calls (analytics, profile metadata) ignore it but leaving it as a shared header does no harm.

Save the API configuration. You haven't added any calls yet — those come in subsequent steps.

```
{
  "api_name": "Sprout Social",
  "base_url": "https://api.sproutsocial.com/v1",
  "shared_headers": {
    "Authorization": "Bearer <private: your_bearer_token>",
    "Content-Type": "application/json"
  }
}
```

**Expected result:** Both the API Connector and Toolbox plugins are installed. The 'Sprout Social' API appears in the API Connector with base URL and the Private Authorization header configured. Ready to add individual calls.

### 3. Create the Get Client Metadata Call and Cache Profile IDs

The /metadata/client endpoint returns all social profiles (accounts) connected to your Sprout Social organization. You need profile IDs for every analytics and messaging call — fetch them once and store them in Bubble state or a data type.

Add a new call under Sprout Social:
- Name: Get Client Metadata
- Method: GET
- Path: /metadata/client
- Use as: Data

Click 'Initialize call'. Sprout returns a JSON object with a `data.profiles` array. Each profile object includes: id (the numeric profile ID), network_type (TWITTER, FACEBOOK, INSTAGRAM_BUSINESS, LINKEDIN_COMPANY), name, and handle.

After successful initialization, create a Bubble data type or option set to store profiles:

Option set 'SproutProfile' with attributes: profile_id (text), network_type (text), display_name (text). Create one option per profile using the IDs from the initialization response.

Alternatively, use a Bubble custom state on your page of type list of texts to store profile IDs during the session — simpler for prototyping, but not persisted between page loads.

For a production dashboard, store profiles in a Bubble data type 'SproutProfile' with a refresh workflow (admin button) that re-fetches /metadata/client and updates stored records when new social accounts are added to Sprout.

The most important rule from the brief: do NOT call /metadata/client on every page interaction or in a loop. Call it once on page load, store the results, and reuse the cached list for the session.

```
{
  "method": "GET",
  "url": "https://api.sproutsocial.com/v1/metadata/client",
  "headers": {
    "Authorization": "Bearer <private>"
  },
  "example_response": {
    "data": {
      "profiles": [
        {
          "id": 12345678,
          "network_type": "TWITTER",
          "name": "YourBrand Twitter",
          "handle": "yourbrand"
        },
        {
          "id": 87654321,
          "network_type": "LINKEDIN_COMPANY",
          "name": "YourBrand LinkedIn",
          "handle": "your-brand"
        }
      ]
    }
  }
}
```

**Expected result:** The 'Get Client Metadata' call initializes successfully showing your connected Sprout profiles. Profile IDs are stored in a Bubble option set or data type for use in subsequent analytics calls.

### 4. Create the Analytics Call with Bracket-Notation Parameters and Date Conversion

The analytics endpoint is the heart of this integration. Add a new call under Sprout Social:
- Name: Get Profile Metrics
- Method: GET
- Path: /analytics/profiles
- Use as: Data

Now add the query parameters. Here is the bracket-notation requirement: to filter by multiple social profiles, you add multiple parameters with the same key name but with [] suffix. In Bubble's API Connector call, add parameters:
- Key: customer_profile_id[] (with brackets), Value: <dynamic: profile_id_1>
- Key: customer_profile_id[] (same key name with brackets again), Value: <dynamic: profile_id_2>

For a single-profile dashboard, one customer_profile_id[] parameter is sufficient.

Also add:
- Key: start_time, Value: <dynamic: iso_start_date>
- Key: end_time, Value: <dynamic: iso_end_date>

These date parameters must be ISO 8601 format strings — Bubble's date type is not ISO 8601 natively. Use the Toolbox plugin to convert.

Before calling the analytics API in a workflow, add a 'Run JavaScript' action (from the Toolbox plugin):

```
var startDate = new Date(BUBBLE_START_DATE);
var endDate = new Date(BUBBLE_END_DATE);
return JSON.stringify({start: startDate.toISOString(), end: endDate.toISOString()});
```

Store the returned JSON in a custom state, then parse start and end ISO strings to pass to the analytics call.

Initialize the Get Profile Metrics call using a real profile ID and a date range within the last 30 days (e.g., start = 7 days ago, end = today) in ISO 8601 format (e.g., 2026-07-03T00:00:00.000Z). You need a real valid response to detect fields.

RapidDev's team has built social analytics dashboards in Bubble connecting to multiple data sources including Sprout Social — for complex multi-platform reporting requirements, book a free scoping call at rapidevelopers.com/contact.

```
{
  "method": "GET",
  "url": "https://api.sproutsocial.com/v1/analytics/profiles",
  "headers": {
    "Authorization": "Bearer <private>"
  },
  "query_parameters": {
    "customer_profile_id[]": "<profile_id_1>",
    "start_time": "<iso_8601_start>",
    "end_time": "<iso_8601_end>"
  },
  "toolbox_js_date_conversion": "var s = new Date(BUBBLE_START_DATE); var e = new Date(BUBBLE_END_DATE); return JSON.stringify({start: s.toISOString(), end: e.toISOString()});",
  "example_analytics_response": {
    "data": [
      {
        "dimensions": { "customer_profile_id": 12345678 },
        "metrics": {
          "impressions": 45230,
          "engagements": 1847,
          "link_clicks": 312,
          "post_count": 14
        }
      }
    ]
  }
}
```

**Expected result:** The 'Get Profile Metrics' call initializes with a real analytics response containing impressions, engagements, and other metrics for the specified profile and date range. Bubble detects the nested field paths in the data array.

### 5. Build the Analytics Dashboard with Network-Specific Field Handling

Now build the dashboard UI that handles varying field names across social networks.

On your analytics page, add:
- A date range picker group: Start Date picker and End Date picker
- A profile selector: Dropdown or multi-select from your SproutProfile option set
- A 'Refresh' button that triggers the analytics workflow
- Metric display cards: Impressions, Engagements, Engagement Rate
- A freshness disclaimer text: 'Data reflects posts published before [2 days ago formatted]. Social platforms deliver analytics with a 24-72 hour delay.'

Workflow on Refresh button click:
1. Toolbox Run JavaScript: convert Start Date picker value and End Date picker value to ISO 8601 strings → store in custom state 'DateRange' (text type, stores JSON string)
2. Call Sprout Social 'Get Profile Metrics' API with: profile_id from selected profile, iso_start_date from DateRange parsed start, iso_end_date from DateRange parsed end
3. Store API result in custom state 'AnalyticsData'

For the metric cards, map values:
- Impressions: AnalyticsData's data first item's metrics impressions
- Engagements: AnalyticsData's data first item's metrics engagements
- Engagement Rate: (AnalyticsData's data first item's metrics engagements / AnalyticsData's data first item's metrics impressions) * 100 formatted as percent

Network-specific field note: the metrics object structure is generally consistent across networks at the analytics/profiles level (impressions, engagements). Post-level analytics (/analytics/posts) have more network-specific variation — handle those with conditional visibility: show 'post_impressions' only when the network_type is FACEBOOK, show 'impressions' for TWITTER, etc. Use Bubble's Conditional tab on text elements to show/hide fields based on the network_type value.

**Expected result:** The analytics dashboard shows impressions, engagements, and engagement rate for the selected profile and date range. The freshness disclaimer is visible. Changing the date range and clicking Refresh updates the metrics. The engagement rate formula calculates correctly from live API data.

### 6. Add Post Scheduling and Finalize Privacy Rules

Add a message scheduling call to round out the integration:

- Name: Schedule Post
- Method: POST
- Path: /message/create
- Body type: JSON
- Body: { "profile_ids": ["<dynamic: profile_id>"], "text": "<dynamic: post_text>", "scheduled_send_time": "<dynamic: iso_scheduled_time>" }
- Use as: Action
- Initialize with a test profile ID, short test text, and an ISO 8601 timestamp for a future time

Build the compose form:
- Post Text multiline input
- Schedule Time date/time picker
- Profile dropdown (from SproutProfile option set)
- 'Schedule Post' button

Workflow:
1. Run JavaScript (Toolbox): convert date picker value to ISO string → store in custom state 'ScheduledIso'
2. Call Sprout Social 'Schedule Post' with profile_id, post_text, and iso_scheduled_time from the custom state
3. Show success message
4. Reset form inputs

Before going live, configure Bubble privacy rules:
- If you store any post content or analytics data in Bubble data types, set privacy rules to restrict read access to authorized users only
- If this is a multi-user app, ensure users only see data for their own organization's connected profiles
- Test the privacy rules by accessing your Bubble app's Data API URL while not logged in — no Sprout data should be accessible

```
{
  "method": "POST",
  "url": "https://api.sproutsocial.com/v1/message/create",
  "headers": {
    "Authorization": "Bearer <private>",
    "Content-Type": "application/json"
  },
  "body": {
    "profile_ids": ["<dynamic: profile_id>"],
    "text": "<dynamic: post_text>",
    "scheduled_send_time": "<dynamic: iso_scheduled_time>"
  }
}
```

**Expected result:** The full integration is working: analytics dashboard shows metrics, compose form schedules posts, and Bubble privacy rules protect stored data. The Sprout Social panel is functional as a standalone tool inside your Bubble app.

## Best practices

- Confirm Sprout Social Advanced plan access before building — the 403 error on lower-tier plans can be mistaken for an implementation bug, wasting significant debugging time.
- Mark the Authorization header Private in the API Connector — Bubble's server-side proxy handles all calls, and the token should never reach the browser or be visible in client-side network traffic.
- Cache profile IDs from /metadata/client in a Bubble option set or data type on page load. Avoid calling /metadata/client on every page interaction or in a workflow loop — it consumes WU unnecessarily for data that changes infrequently.
- Always include a data freshness disclaimer on analytics displays. Social platform analytics lag 24-72 hours — showing a 'Data as of [2 days ago]' note prevents users from interpreting the lag as a bug.
- Convert all date values to ISO 8601 UTC format using JavaScript's toISOString() method before passing to Sprout's API. Bubble's date type is not ISO 8601 by default.
- Handle network-specific field variations with Bubble's Conditional tab rather than trying to normalize field names in a single display element. Different social networks return different metric fields — build for diversity, not assumed uniformity.
- WU awareness: analytics API calls for large date ranges or multiple profiles can be slow and expensive in WU terms. Cache results in Bubble custom states and require a user action (Refresh button) to re-fetch rather than polling automatically.
- Configure Bubble privacy rules on any data type storing social analytics or post content — especially important in multi-user apps where different team members should only access data for their own accounts.

## Use cases

### Custom Social Analytics Dashboard

Build a reporting page in Bubble that shows weekly engagement metrics (impressions, likes, shares, comments) for all connected social profiles using Sprout's /metrics/profiles endpoint. Display network-specific metrics with conditional visibility for fields that only exist on certain platforms. Add date range pickers to query historical periods.

Prompt example:

```
Page load: Call Sprout API 'Get Profile Metrics' with start date = 7 days ago formatted as ISO 8601, end date = today formatted as ISO 8601, profiles[]=profile_id_1, profiles[]=profile_id_2. Display results in metric cards: Impressions (sum), Engagements (sum), Engagement Rate (engagements/impressions * 100 formatted as %). Add a '72-hour data delay' notice above all metric cards.
```

### Campaign Performance Tracker

Combine Sprout analytics with campaign records stored in your Bubble database. Each Bubble Campaign record includes a start date, end date, and associated social profile IDs. A dashboard page queries Sprout's analytics for the campaign date range and displays performance against targets set in the Bubble database.

Prompt example:

```
On Campaign Detail page load: Call Sprout API 'Get Post Metrics' with date range from Campaign's start_date to Campaign's end_date, profiles = Campaign's social_profile_ids list. Display metrics alongside Campaign's target_impressions and target_engagements fields. Calculate attainment percentage: actual/target * 100.
```

### Scheduled Post Management Panel

Display all Sprout-scheduled posts for selected social profiles in a Bubble repeating group. Show post text, platform, scheduled time, and status. Allow team members to view and delete scheduled posts without opening Sprout's dashboard. Add a compose form that creates new scheduled posts via POST to the messages endpoint.

Prompt example:

```
Profile selector dropdown with Sprout profiles from /metadata/client cache. On profile select: Call Sprout API 'Get Scheduled Posts' with selected profile_id. Repeating group shows post text, scheduled_at (formatted as readable date/time), network icon. Delete button per row calls Sprout API 'Delete Post' with post ID.
```

## Troubleshooting

### Every API call returns 403 Forbidden even with a valid Bearer token

Cause: The Sprout Social account is on a Standard or Professional plan. API access is restricted to Advanced plan and above. A 403 occurs regardless of correct OAuth implementation on lower-tier plans.

Solution: Contact your Sprout Social account manager to verify plan level. Upgrade to the Advanced plan to unlock API access. There is no sandbox environment or trial API access available.

### Analytics endpoint returns empty data arrays with no error

Cause: The date range falls within the data lag window. Twitter analytics lag ~24 hours; Facebook and Instagram lag 48-72 hours. Querying data from today or yesterday may return empty results even though posts were published.

Solution: Set the end_time in analytics queries to at least 48-72 hours in the past to ensure data availability. Display a UI note explaining the data lag to users. For 'current' stats, show the last available data with a 'as of [date]' timestamp rather than showing an empty state.

### Initialize call fails with 'There was an issue setting up your call'

Cause: The Initialize call requires a real successful response. If the Bearer token is invalid, the profile ID doesn't exist, or the date range returns empty data, initialization fails and Bubble cannot detect response field shapes.

Solution: During initialization, manually enter a real valid profile ID from /metadata/client. Set the date range to a period 7-30 days ago (not the current day) to ensure data exists. If you receive an empty data array but a 200 response, try a wider date range. Once initialization completes with a populated response, Bubble detects the field structure.

### Analytics fields differ between Twitter and LinkedIn results — some fields show 'undefined' or empty

Cause: Sprout Social analytics response schemas vary by social network. Facebook uses post_impressions, Twitter and LinkedIn use impressions but calculate them differently. Assuming all networks return identical field names causes some fields to be undefined for certain networks.

Solution: Use Bubble's Conditional tab on metric display text elements to show network-specific fields based on network_type. For example: show 'post_impressions' metric text only when the current profile's network_type is FACEBOOK. For cross-network totals, sum only fields that exist across all networks (engagements and post_count are fairly universal).

### Scheduled post returns 400 Bad Request with a date/time error

Cause: The scheduled_send_time value is not in valid ISO 8601 UTC format, or the scheduled time is in the past relative to UTC. Bubble's date picker returns local time; without conversion, the time may be interpreted as past.

Solution: Use the Toolbox plugin's Run JavaScript action to convert: var d = new Date(BUBBLE_DATE); return d.toISOString(); — this always produces UTC ISO 8601 format. Verify the output ends with 'Z' (UTC indicator). Ensure the converted time is at least 5 minutes in the future.

## Frequently asked questions

### Why does my Sprout Social API call return 403 even though the Bearer token is correct?

A 403 from Sprout Social's API most commonly means the account is on a Standard or Professional plan — these plans do not include API access regardless of token validity. It can also occur if the specific endpoint you're calling requires a higher plan tier than your Advanced subscription includes (some listening endpoints require additional add-ons). Contact your Sprout account manager to confirm which API endpoints are available for your specific plan level.

### Why does the analytics endpoint return empty data even though I published posts recently?

Sprout Social's analytics have a processing lag: Twitter data is available approximately 24 hours after publication, Facebook and Instagram data lags 48-72 hours. Querying analytics for today or yesterday will often return empty results even with valid posts. Always set your analytics end date to at least 48-72 hours in the past to query data that has finished processing. For 'recent' performance, query the last 7 days with end_time set to 3 days ago.

### How do I handle the bracket-notation array parameters in Bubble's API Connector?

In Bubble's API Connector call configuration, add multiple query parameters with the same key name including the [] suffix. For example, add two separate parameters both named 'customer_profile_id[]' with different dynamic values. Bubble passes them as separate query string entries (customer_profile_id[]=id1&customer_profile_id[]=id2) which is exactly what Sprout's API expects for array inputs. This is not a bug — it's an intentional multi-value parameter pattern.

### Do I need the Toolbox plugin for this integration?

Yes, if you want to accept date inputs from Bubble's date picker for analytics queries. Bubble's date type is not ISO 8601 format by default, and Sprout's API requires ISO 8601 date strings. The Toolbox plugin's 'Run JavaScript' action lets you call new Date(bubbleDate).toISOString() which returns the correct UTC ISO format. Without Toolbox, you would need to hardcode date strings or find another way to format dates, which is not practical for a real analytics dashboard.

### Can I display analytics from multiple social profiles side by side?

Yes. The /analytics/profiles endpoint accepts multiple profile IDs via the bracket-notation parameter (customer_profile_id[]=id1, customer_profile_id[]=id2). The response returns a data array with one entry per profile, each with a dimensions object containing the profile ID and a metrics object with the numbers. In Bubble, bind a repeating group to this data array and display each profile's metrics in separate rows or columns.

### How do I show the most recent available data given the analytics lag?

Set your analytics query's end_time to 3 days ago (Current Date/Time minus 3 days, converted to ISO 8601 UTC). Display a 'Data as of [3 days ago formatted]' timestamp above the metrics. This approach always queries data that has finished processing rather than potentially empty recent data. Some teams prefer to show 'Last 7 days ending 3 days ago' rather than 'Last 7 days' to set accurate expectations.

---

Source: https://www.rapidevelopers.com/bubble-integrations/sprout-social
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/sprout-social
