Skip to content

02 Get your data in /Integrations

Sprinklr

Send the messages matched by your Sprinklr Listening Topics to Dimension Labs with the universal API.

  1. S SprinklrTickets, chats, calls, reviews
  2. ⟳ Your jobPull new · map fields · every 15 min
  3. { } Universal APIPOST /track · /trackhistorical
  4. Dimension LabsRead in full · enriched daily

This brings in the messages your Sprinklr Listening Topics already collect: public posts, comments and reviews about your brand across the channels you listen to, each one as a customer message. You run it on your side: a small script pulls from Sprinklr’s Listening API and posts each message to the universal API.

  • A Sprinklr account with Listening, and Listening API access switched on. Sprinklr says Listening is not enabled for the API by default: ask Sprinklr Support, when Listening is part of the use case on your License Order Form. Without it, the API answers “Streaming in listening is not enabled for you. Please contact support”.
  • A Sprinklr user who can see the Topics you want. The API key inherits that user’s permissions, and a Topic the user can’t see returns nothing rather than an error.
  • Your Sprinklr environment ({env}, for example prod2). Ask your Sprinklr admin if you don’t know it.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Text Sprinklr can’t pass on. Some channels’ resyndication policies, X in particular, stop Sprinklr from returning the text over the API. Those messages come back with an empty content.text and "apiStatus": "Removed fields because of resyndication policy". Sprinklr says enabling full X (Twitter) syndication needs a request to Sprinklr Support. The code below skips messages with no text.

Sprinklr’s steps are in Generate an API Key and Secret.

  1. Sign in to your Sprinklr environment and open Developer Tools. (Key generation has moved there from the Developer Portal.)
  2. Select Create App, or open an existing app. In its Credentials section, select Generate API Key and copy the API key and secret.
  3. Generate an access token from the same screen. Store the key, secret and token in a secrets manager.
  4. Send every request with Authorization: Bearer ACCESS_TOKEN and Key: API_KEY. Check your setup with GET https://api3.sprinklr.com/{env}/api/v2/me.
  5. Find your Topic ids with the Listening Topic APIs. The query takes ids, never names.
Terminal window
curl -X POST "https://api3.sprinklr.com/$SPRINKLR_ENV/api/v3/listening/query/stream" \
-H "Authorization: Bearer $SPRINKLR_ACCESS_TOKEN" -H "Key: $SPRINKLR_API_KEY" \
-H "Content-Type: application/json" -H "Accept: application/json" \
-d '{"sinceTime":1759276800000,"untilTime":1761436800000,"timeField":"SYSTEM_CREATED_TIME",
"details":{"widgetType":"STREAM"},"filters":[{"dimension":"TOPIC","filterValues":["TOPIC_ID"]}],
"metric":"MENTIONS","sortKey":"SYSTEM_CREATED_TIME","messageFormatOptions":"strip_html","rows":1000,"start":0}'
  • POST /api/v3/listening/query/stream returns the messages themselves. sinceTime and untilTime are epoch milliseconds.
  • Incremental: Sprinklr recommends timeField: "SYSTEM_CREATED_TIME" for a 15-minute poll. It windows on when Sprinklr took the message in, so a post from three days ago that Sprinklr picked up five minutes ago is not missed. The cursor is the newest createdTime (Sprinklr’s ingestion time).
  • Pagination: page with start and rows while data.hasMore is true. One request returns at most 1,000 messages, and you can’t fetch more than 10,000 messages in the same time window, so the code splits a busier window in two.
  • messageFormatOptions: "strip_html" strips HTML from the text.
  • Sprinklr can report errors in the errors array of a 200, so the code checks it on every answer.
  • Rate limits (Sprinklr’s): a sliding window set by your account tier: Standard 60 requests a minute (3,000 an hour, 72,000 a day), Professional 120, Enterprise 300, or a custom limit. A 429 comes with Retry-After and X-RateLimit-Reset.

Each message is one incoming message under its own sessionId. Messages with brandPost: true come from your own accounts and are skipped.

Dimension LabsSprinklrNotes
textcontent.textSkip empty text; check apiStatus to see why it is empty.
userIdmessageIdNo author id is documented on Listening messages, so use the message’s id.
incomingAlways true.
sessionIdmessageIdSprinklr’s unique message id, prefixed: sprinklr-ACCOUNT_600049002_....
conversationIdmessageIdSame value as sessionId.
dimensionlabs_timestampchannelCreatedTimeWhen the message was created on its channel, in milliseconds. Falls back to createdTime.
platformJsonchannelType, subType, accountType, language, channelMessageId, sourceIdchannelType tells the networks apart; subType tells a post from a comment, reply or mention.

Personal data. Don’t send senderProfile fields or permalink, which can hold handles. Message text can hold names and order details: redact it before you send. See PII redaction.

// sprinklr.mjs. Environment: SPRINKLR_ENV (for example prod2), SPRINKLR_API_KEY, SPRINKLR_ACCESS_TOKEN,
// SPRINKLR_TOPIC_IDS (comma-separated Listening Topic ids), 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://api3.sprinklr.com/${process.env.SPRINKLR_ENV}/api/v3`;
const ROWS = 1000; // Sprinklr's maximum per request
const id = (x) => String(x).replace(/:/g, '-');
// Retries Sprinklr's rate limits (429) and server errors (5xx), honoring Retry-After or X-RateLimit-Reset.
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.SPRINKLR_ACCESS_TOKEN}`, Key: process.env.SPRINKLR_API_KEY,
'Content-Type': 'application/json', Accept: 'application/json' } });
if (res.ok) {
const json = await res.json();
if (json.errors?.length) throw new Error(`Sprinklr returned errors: ${JSON.stringify(json.errors)}`); // Sprinklr can report errors on a 200
return json;
}
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Sprinklr returned ${res.status}: ${await res.text()}`);
const reset = Number(res.headers.get('x-ratelimit-reset')) * 1000 - Date.now();
await sleep(Number(res.headers.get('retry-after')) * 1000 || (reset > 0 ? reset + 1000 : 2 ** attempt * 1000));
}
}
// Every message Sprinklr took into the Topics between `since` and `until` (Sprinklr's own ingestion time).
// One time window can't page past 10,000 messages, so a busier window is split in two.
async function* stream(since, until) {
const topics = process.env.SPRINKLR_TOPIC_IDS.split(',').map((x) => x.trim()).filter(Boolean);
const all = [];
for (let start = 0; ; start += ROWS) {
if (start >= 10000) {
const mid = since + Math.floor((until - since) / 2);
if (mid === since) throw new Error('Too many Sprinklr messages in one millisecond to page through.');
yield* stream(since, mid);
yield* stream(mid, until);
return;
}
const { data } = await post('/listening/query/stream', { sinceTime: since, untilTime: until, timeField: 'SYSTEM_CREATED_TIME',
details: { widgetType: 'STREAM' }, filters: [{ dimension: 'TOPIC', filterValues: topics }], metric: 'MENTIONS',
sortKey: 'SYSTEM_CREATED_TIME', messageFormatOptions: 'strip_html', rows: ROWS, start });
all.push(...(data?.result ?? []));
if (!data?.hasMore) break;
}
yield all;
}
// Sends every Listening message Sprinklr took in at or after `since` (Unix ms). Returns the new `since`.
export async function syncSprinklr({ since, historical = false }) {
let newest = since;
for await (const batch of stream(since, Date.now())) {
for (const m of batch) {
newest = Math.max(newest, m.createdTime ?? since);
const text = m.content?.text?.trim();
if (!text || m.brandPost) continue; // empty text (see apiStatus), or a post by one of your own accounts
const ts = toMillis(m.channelCreatedTime || m.createdTime);
const sessionId = `sprinklr-${id(m.messageId)}`;
await sendToDimensionLabs({
text,
userId: sessionId,
incoming: true,
sessionId,
conversationId: sessionId,
dimensionlabs_timestamp: ts,
platformJson: { channelType: m.channelType, subType: m.subType, accountType: m.accountType, language: m.language,
channelMessageId: m.channelMessageId, sourceId: m.sourceId },
}, { historical: historical || ts < Date.now() - HOUR });
}
}
return newest;
}

Save the code as sprinklr.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncSprinklr as sync } from './sprinklr.mjs';.

  • History first: node run.mjs 2026-01-01 sends every message Sprinklr took into the Topics 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.

If you have messages in a file instead, upload it and check it first with the file checker, then run a backfill.

Checked against Sprinklr’s documentation on 2026-10-26.