Skip to content

02 Get your data in /Integrations

Brandwatch Consumer Research

Send the mentions matched by a Brandwatch Consumer Research Query to Dimension Labs with the universal API.

  1. BC Brandwatch Consumer ResearchTickets, 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 mentions your Brandwatch Consumer Research Query already collects: forum and blog posts, reviews, news and social posts about your brand, each one as a customer message with Brandwatch’s sentiment and categories alongside. You run it on your side: a small script pulls from the Consumer Research API and posts each mention to the universal API.

  • A Brandwatch Consumer Research account and a Query that collects the mentions you want.
  • A Consumer Research user with the Regular or Admin role. Brandwatch says only those users can use the API.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Sources Brandwatch can’t pass in full. Brandwatch’s Data Restrictions page says some data packs limit what the API returns:

  • X (Twitter): the full text and most metadata are stripped. Brandwatch suggests taking the post id from guid and fetching the text from the X API yourself; see the X guide, and its note on X’s terms.
  • Online News: only a 256-character snippet is available.
  • LinkedIn: no snippet and no full text.

Brandwatch’s changelog also announced Reddit restrictions from January 2026. Ask your account manager which restrictions apply to your data packs. Mentions with no text are skipped by the code below.

Brandwatch’s steps are in Authentication.

  1. Get an access token with the API user’s username and password, once:
Terminal window
curl -X POST --data-urlencode "password=$BRANDWATCH_PASSWORD" \
"https://api.brandwatch.com/oauth/token?username=api.user@yourcompany.com&grant_type=api-password&client_id=brandwatch-api-client"
  1. The answer’s access_token is valid for one year by default (expires_in is in seconds). Keep it in a secrets manager and renew it before it expires. If your user can switch between organizations, add platform_client_id to the request; Brandwatch support gives you the ids.
  2. Send it as Authorization: Bearer ACCESS_TOKEN.
  3. Find your Project ID and Query ID with Brandwatch’s Retrieving Projects and Retrieving Queries calls.
Terminal window
curl -G "https://api.brandwatch.com/projects/PROJECT_ID/data/mentions/fulltext" \
-H "Authorization: Bearer $BRANDWATCH_TOKEN" \
--data-urlencode "queryId=QUERY_ID" \
--data-urlencode "startDate=2026-10-01T00:00:00.000+0000" --data-urlencode "endDate=2026-10-26T00:00:00.000+0000" \
--data-urlencode "pageSize=1" --data-urlencode "page=0"
  • /data/mentions returns a snippet of each mention, the part that best matches the Query. /data/mentions/fulltext returns the full text, which is what you want to enrich.
  • Find the text field once. Brandwatch’s docs don’t name the field that holds the full text in a fulltext answer. Run the call above with pageSize=1, look at the mention, and set BRANDWATCH_TEXT_FIELD to the name of the field that holds the whole post.
  • Dates: startDate and endDate are required and filter on when the mention was posted. Format: 2026-10-01T00:00:00.000+0000; encode the + as %2B (--data-urlencode and the code do this).
  • Incremental: each mention has added, the time it was added to the Query, which can be well after it was posted. The code reads the last 24 hours of posting dates (BRANDWATCH_LOOKBACK_HOURS) and keeps mentions added since the last run, with the 5-minute buffer Brandwatch’s polling tutorial uses. A Query backfill resets added, so backfilled mentions are sent again; the duplicates are removed.
  • Pagination: pageSize goes up to 5,000, and page counts from 0. Page-based reads stop at 10,000 mentions, so the code splits a busier date range in two.
  • Rate limits (Brandwatch’s): 30 requests every 10 minutes per client by default, with a 429 past that. The x-rate-limit and x-rate-limit-used headers show your usage. Brandwatch asks for one request at a time rather than in parallel, and your account manager can discuss a higher limit.

Each mention is one incoming message under its own sessionId. Brandwatch gives no reliable flag for posts by your own accounts, so exclude your own accounts in the Query itself.

Dimension LabsBrandwatchNotes
textthe full-text field (see step 2)Skip mentions with no text.
userIdresourceIdAuthor ids differ by network, so use the mention’s id. Don’t send author or fullname.
incomingAlways true.
sessionIdresourceIdBrandwatch’s unique id for the mention. Prefix it: brandwatch-c54d12f5107496bac30b5ddc3478bbab.
conversationIdresourceIdSame value as sessionId.
dimensionlabs_timestampdateWhen the mention was posted, like 2020-06-29T22:45:43.000+0000. Convert with toMillis.
platformJsonqueryId, contentSource, domain, sentiment, language, countryCode, threadId, threadEntryType, guid, tags, categoriescontentSource tells the networks apart. threadId (X and Facebook only) groups mentions from one thread.

Personal data. Don’t send author, fullname, avatarUrl or url, which can hold names and handles. Mention text can hold names too: redact it before you send. See PII redaction.

// brandwatch.mjs. Environment: BRANDWATCH_TOKEN, BRANDWATCH_PROJECT_ID, BRANDWATCH_QUERY_ID,
// BRANDWATCH_TEXT_FIELD (the field that holds the full text in a fulltext response; see step 2), DL_API_KEY.
// Optional: BRANDWATCH_LOOKBACK_HOURS (default 24).
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const BUFFER = 5 * 60 * 1000; // Brandwatch's polling tutorial uses a 5-minute buffer on `added`
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const API = `https://api.brandwatch.com/projects/${process.env.BRANDWATCH_PROJECT_ID}/data/mentions/fulltext`;
const bwTime = (ms) => new Date(ms).toISOString().replace('Z', '+0000'); // 2026-10-01T00:00:00.000+0000
// Retries Brandwatch's rate limit (429: 30 calls per 10 minutes by default) and server errors (5xx).
async function get(params) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(`${API}?${new URLSearchParams(params)}`, { headers: { Authorization: `Bearer ${process.env.BRANDWATCH_TOKEN}` } });
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Brandwatch returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 60 * 2 ** attempt) * 1000);
}
}
// Every mention posted between `start` and `end`. Page-based reads stop at 10,000 mentions, so a busier window is split in two.
async function* mentions(start, end) {
const base = { queryId: process.env.BRANDWATCH_QUERY_ID, startDate: bwTime(start), endDate: bwTime(end),
pageSize: '5000', orderBy: 'date', orderDirection: 'asc' };
const first = await get({ ...base, page: '0' });
if (first.resultsTotal > 10000 && end - start > HOUR) {
const mid = start + Math.floor((end - start) / 2);
yield* mentions(start, mid);
yield* mentions(mid, end);
return;
}
yield first.results ?? [];
for (let page = 1; page * 5000 < Math.min(first.resultsTotal ?? 0, 10000); page++) {
yield (await get({ ...base, page: String(page) })).results ?? [];
}
}
// Sends every mention added to the Query at or after `since` (Unix ms). Returns the new `since`.
export async function syncBrandwatch({ since, historical = false }) {
const field = process.env.BRANDWATCH_TEXT_FIELD;
if (!field) throw new Error('Set BRANDWATCH_TEXT_FIELD to the field that holds the full text (see step 2 of the guide).');
let newest = since;
const start = historical ? since : since - Number(process.env.BRANDWATCH_LOOKBACK_HOURS || 24) * HOUR; // mentions are often added late
for await (const batch of mentions(start, Date.now())) {
for (const m of batch) {
const added = toMillis(m.added);
if (!historical && added < since - BUFFER) continue; // seen on an earlier run
newest = Math.max(newest, added);
const text = String(m[field] ?? '').trim();
if (!text) continue; // X posts and LinkedIn data come without text
const ts = toMillis(m.date);
const sessionId = `brandwatch-${m.resourceId}`;
await sendToDimensionLabs({
text,
userId: sessionId,
incoming: true,
sessionId,
conversationId: sessionId,
dimensionlabs_timestamp: ts,
platformJson: { queryId: m.queryId, contentSource: m.contentSource, domain: m.domain, sentiment: m.sentiment,
language: m.language, countryCode: m.countryCode, threadId: m.threadId, threadEntryType: m.threadEntryType,
guid: m.guid, tags: m.tags, categories: m.categories },
}, { historical: historical || ts < Date.now() - HOUR });
}
}
return newest;
}

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

  • History first: node run.mjs 2026-01-01 sends every mention posted since that date to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends new mentions 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.

The default limit of 30 requests every 10 minutes is the constraint. A 15-minute run makes one or two calls; a history load of a busy Query makes many, and the code waits whenever Brandwatch returns a 429. If you have mentions in a file instead, upload it and check it first with the file checker, then run a backfill.

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