02 Get your data in /Integrations
Sprout Social
Send the comments, mentions and messages in your Sprout Social inbox to Dimension Labs with the universal API.
- SS Sprout SocialTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in the messages your Sprout Social profiles receive (comments, mentions, direct messages and reviews across your connected networks) and your team’s replies within Sprout cases. You run it on your side: a small script pulls from the Sprout Social API and posts each message to the universal API.
What you need
Section titled “What you need”- A Sprout plan that enables API access. Sprout says to ask your sales representative or support if you aren’t sure your plan includes it.
- A Sprout user with the API Permissions permission, who has accepted Sprout’s Analytics API Terms of Service under Settings > Global Features > API.
- For X data: every Sprout user with API Permissions has to accept the Sprout API X Content End User License Agreement (shown at the top of the same API page), and Sprout says customers go through a brief X review and approval before they can reach X data through the API.
- A Dimension Labs API key for a new Universal API source. See Your API key.
What the API leaves out. Sprout’s API doesn’t include paid (ad account) data, Listening data from X, or Listening message-level data from Reddit. It can’t pass on Yelp, Trustpilot, Tripadvisor or Glassdoor reviews, and Google Business Profile (GOOGLE_MY_BUSINESS_*) messages are limited to the last 30 days. For reviews, use the Trustpilot or Google reviews guide instead.
1. Get API access in Sprout Social
Section titled “1. Get API access in Sprout Social”Sprout’s steps are in its API documentation.
- Log in to Sprout and go to Settings > Global Features > API. Accept the Analytics API Terms of Service if you haven’t.
- In API Token Management, click Generate API Token, enter a name and copy the token. Store it securely. (Sprout also supports OAuth 2.0 machine-to-machine clients, which it recommends; the token is simpler for one script.)
- Send it as
Authorization: Bearer YOUR_TOKENwithAcceptandContent-Typeset toapplication/json. - Find your ids once:
GET https://api.sproutsocial.com/v1/metadata/clientreturns yourcustomer_id, andGET https://api.sproutsocial.com/v1/CUSTOMER_ID/metadata/customerlists each profile’scustomer_profile_idand thegroupsit belongs to.
2. Pull new messages
Section titled “2. Pull new messages”curl -X POST "https://api.sproutsocial.com/v1/CUSTOMER_ID/messages" \ -H "Authorization: Bearer $SPROUT_API_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \ -d '{"filters":["group_id.eq(78910)","customer_profile_id.eq(1234, 5678)","created_time.in(2026-10-01T00:00:00..2026-10-26T00:00:00)"], "fields":["guid","created_time","text","post_type","from.guid","profile_guid","case_id"], "limit":100,"sort":["created_time:asc"],"timezone":"Etc/UTC"}'- The Messages endpoint returns messages published by or received by your profiles. Without
fields, it returns onlyguid. - Incremental: filter on
created_time.in(start..end). Dates without a time are read as midnight, in thetimezoneyou send. - Pagination:
limitgoes up to 100. Passpaging.next_cursoraspage_cursoruntil a response has nopaging. Only the next page can be fetched. profile_guidequalsfrom.guidwhen your profile wrote the message. That is how the code tells your replies from the public’s.- The Messages endpoint returns no thread id.
case_idis filled when a message belongs to a Sprout case, so cases are what group a customer’s message with your reply. - Rate limits (Sprout’s): 60 requests per minute and 250,000 requests per month. A
202means the answer isn’t ready yet: retry it.
3. Map the fields
Section titled “3. Map the fields”A message in a case joins that case’s conversation. A message from the public outside a case is a conversation of its own.
| Dimension Labs | Sprout Social | Notes |
|---|---|---|
text | title and text | Join them with a line break when both are present. |
userId | from.guid | The author’s native profile id with Sprout’s prefix, colons replaced: sprout-fbpr-1112345. Fall back to the sessionId. |
incoming | from.guid, profile_guid | false when they are equal (your profile wrote it), true otherwise. |
sessionId | case_id, else guid | sprout-case-CASEID, or sprout-GUID. Sprout’s ids contain colons: replace them with -. |
conversationId | same as sessionId | |
dimensionlabs_timestamp | created_time | ISO 8601 in UTC. Convert with toMillis. |
platformJson | guid, network, post_type, post_category, customer_profile_id, case_id, language_code | post_type tells a comment (FBCR_COMMENT, INSTAGRAM_GRAPH_COMMENT, YOUTUBE_COMMENT) from a DM or a review. |
Personal data. Don’t request from.name, from.screen_name or the internal.sent_by name and email fields. Messages can hold names, phone numbers and order details: redact them before you send. See PII redaction.
4. Send it
Section titled “4. Send it”// sprout-social.mjs. Environment: SPROUT_API_TOKEN, SPROUT_CUSTOMER_ID, SPROUT_GROUP_ID,// SPROUT_PROFILE_IDS (comma-separated customer_profile_id values in that group), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const API = `https://api.sproutsocial.com/v1/${process.env.SPROUT_CUSTOMER_ID}`;const id = (x) => String(x).replace(/:/g, '-'); // Sprout ids look like "fbpr:123"; Dimension Labs ids can't hold colonsconst FIELDS = ['guid', 'created_time', 'text', 'title', 'post_type', 'post_category', 'network', 'from.guid', 'profile_guid', 'customer_profile_id', 'case_id', 'language_code'];
// Retries Sprout's rate limits (429), "not ready" answers (202) and server errors (5xx), honoring Retry-After when it is sent.async function post(path, body) { for (let attempt = 0; ; attempt++) { const res = await fetch(`${API}${path}`, { method: 'POST', body: JSON.stringify(body), headers: { Authorization: `Bearer ${process.env.SPROUT_API_TOKEN}`, Accept: 'application/json', 'Content-Type': 'application/json' } }); if (res.status === 200) return res.json(); if ((res.status !== 202 && res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Sprout returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt * 2) * 1000); }}
const sproutTime = (ms) => new Date(ms).toISOString().slice(0, 19); // 2026-10-01T00:00:00, read in the timezone below
// Sends every inbox message on your profiles created at or after `since` (Unix ms). Returns the new `since`.export async function syncSproutSocial({ since, historical = false }) { let newest = since, cursor; const profiles = process.env.SPROUT_PROFILE_IDS.split(',').map((x) => x.trim()).filter(Boolean).join(', '); do { const page = await post('/messages', { filters: [`group_id.eq(${process.env.SPROUT_GROUP_ID})`, `customer_profile_id.eq(${profiles})`, `created_time.in(${sproutTime(since)}..${sproutTime(Date.now())})`], fields: FIELDS, limit: 100, sort: ['created_time:asc'], timezone: 'Etc/UTC', ...(cursor && { page_cursor: cursor }) }); for (const m of page.data ?? []) { const ts = toMillis(m.created_time); newest = Math.max(newest, ts); const text = [m.title, m.text].filter((x) => x?.trim()).join('\n'); if (!text) continue; const mine = Boolean(m.from?.guid) && m.from.guid === m.profile_guid; // Sprout: equal when your profile wrote it if (mine && !m.case_id) continue; // no thread id outside a case, so your post or reply has nothing to join const sessionId = m.case_id ? `sprout-case-${id(m.case_id)}` : `sprout-${id(m.guid)}`; await sendToDimensionLabs({ text, userId: mine ? `sprout-${id(m.profile_guid)}` : m.from?.guid ? `sprout-${id(m.from.guid)}` : sessionId, incoming: !mine, sessionId, conversationId: sessionId, dimensionlabs_timestamp: ts, platformJson: { messageId: m.guid, network: m.network, postType: m.post_type, postCategory: m.post_category, customerProfileId: m.customer_profile_id, caseId: m.case_id, language: m.language_code }, }, { historical: historical || ts < Date.now() - HOUR }); } cursor = page.paging?.next_cursor; } while (cursor); return newest;}Your own posts and replies outside a case are skipped: with no thread id they have no conversation to join, and on their own they hold no customer words.
5. Run it
Section titled “5. Run it”Save the code as sprout-social.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncSproutSocial as sync } from './sprout-social.mjs';.
- History first:
node run.mjs 2026-01-01sends every message since that date to/trackhistorical. Then start a backfill from the Backfill tab. - Then every 15 minutes: the scheduled job sends new messages to
/track, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. - Re-runs are safe: duplicates are removed automatically.
Each page holds up to 100 messages and Sprout allows 60 requests a minute, so a large history takes a while, and every call counts toward the 250,000 a month. If your plan doesn’t include API access, use a file instead. Sprout’s release notes describe an Export Inbox option in the Smart Inbox gear menu, on its Advanced plan: filter the inbox to the messages you want, pick a date range, and Sprout emails a CSV. Map its columns as in step 3 with the converter, check it with the file checker, upload it, then run a backfill.
Sprout Social reference
Section titled “Sprout Social reference”- Sprout Social API documentation (Accessing the APIs, Messages Endpoint, Message Fields, Post Types, Rate Limits, Limitations)
- Export Inbox message data (release note)
Checked against Sprout Social’s documentation on 2026-10-26.