Skip to content

02 Get your data in /Integrations

Bazaarvoice

Send Bazaarvoice product reviews to Dimension Labs with the universal API.

  1. B BazaarvoiceTickets, 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 product reviews in your Bazaarvoice client instance: each review as a customer message, with its rating and product, and your brand responses when there are any. One conversation per review. You run it on your side: a small script pulls from the Bazaarvoice Conversations API and posts each message to the universal API.

  • Bazaarvoice’s written approval to store review content. The Conversations API is built to display reviews on your own site and apps. Bazaarvoice’s API Terms of Use say you may not store data retrieved through it, and point to its Displayable Content Export, sold separately, for backend data management. Its rate-limit guide says using the Conversations API to build your own data store is limited to clients with prior approval. Sending reviews to Dimension Labs stores them, so get approval from your Bazaarvoice representative before you run this guide, or use the export described at the end of “Run it”.
  • API access enabled for your client instance (if it isn’t, contact your Bazaarvoice representative), and a Portal account with the Technical Contributor or Technical Administrator role and the Ratings and Reviews solution.
  • A Conversations API key (passkey), approved by a Technical Administrator.
  • A key set up to exclude syndicated content, so it can list all your reviews without a product filter (see step 2). Bazaarvoice support can configure this.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Bazaarvoice’s steps are in API key management.

  1. Sign in to Bazaarvoice Portal and pick your client instance in the Instance Selector in the upper-right corner.
  2. Select Settings, then API under Developer.
  3. Select Request API key, choose Conversations API, select Next, and fill out the form. You receive the key details by email.
  4. Your Technical Administrator approves it: in the API Keys section, filter Key details > Status to Pending, open the key, select Enable Key, then Save.
  5. The passkey goes in the passkey query parameter. Production is https://api.bazaarvoice.com; staging (https://stg.api.bazaarvoice.com) has data that doesn’t come from real users.
Terminal window
curl -H "bv-version: 5.4.1" \
"https://api.bazaarvoice.com/data/reviews.json?apiversion=5.4&passkey=$BV_PASSKEY&Filter=LastModificationTime:gte:1759276800&Sort=LastModificationTime:asc&Limit=100&Offset=0"
  • All reviews: a request with no ProductId, AuthorId, CategoryAncestorId, SubmissionId or review Id filter only works with a key configured to exclude syndicated content. Bazaarvoice recommends its Displayable Content Export for pulling everything, native and syndicated (see “Run it”).
  • Incremental: Filter=LastModificationTime:gte:SECONDS returns reviews changed since a time. Bazaarvoice’s time filters take seconds since 1970, UTC. Sort with Sort=LastModificationTime:asc (a sort order is required).
  • Pagination: Limit (up to 100, default 10) and Offset (up to 300,000).
  • Brand responses are in the same review, under ClientResponses, so there is no second call.
  • Errors: by default Bazaarvoice returns 200 OK with the error in the body. The bv-version: 5.4.1 header makes it return real 4xx and 5xx codes. The code sends it, and also checks HasErrors.
  • Rate limits (Bazaarvoice’s): per key, per minute. Your key’s limit is in the Portal and in the X-Bazaarvoice-QPM-Allotted header; X-Bazaarvoice-Quota-Allotted is the hourly quota. Bazaarvoice says increases must be approved and may cost extra.
  • Bazaarvoice’s Terms of Use prohibit XML output: use reviews.json.

Each review is one incoming message under its own sessionId. Each brand response is an outgoing message in the same session.

Dimension LabsBazaarvoiceNotes
textTitle and ReviewText (incoming), ClientResponses[].Response (outgoing)Join the title and body with a line break. Ratings-only reviews have no text and are skipped.
userIdAuthorIdPrefixed bazaarvoice-. Don’t send UserNickname or UserLocation.
incomingtrue for the review, false for a brand response.
sessionIdIdPrefixed bazaarvoice-.
conversationIdId
dimensionlabs_timestampSubmissionTime (review), ClientResponses[].Date (response)Convert with toMillis. A response within a second of the review is moved a second later.
platformJsonRating, ProductId, IsRecommended, ContentLocale, IsSyndicated, IsFeatured, ModerationStatus, CampaignId, Pros, ConsProductId is the field to join to your product data.

Personal data. Reviews carry the author’s nickname and location, and can carry context data such as age. Don’t send them: the code uses AuthorId instead. Reviews can still name people in their text: see PII redaction.

// bazaarvoice.mjs. Environment: BV_PASSKEY, DL_API_KEY. Optional: BV_HOST (defaults to production).
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const HOST = process.env.BV_HOST ?? 'https://api.bazaarvoice.com';
const MAX_OFFSET = 300000;
const at = (d, fallback) => { try { return d ? toMillis(d) : fallback; } catch { return fallback; } };
// Retries Bazaarvoice's rate limits (429) and server errors (5xx), honoring Retry-After when it is sent.
async function get(url) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers: { 'bv-version': '5.4.1', Accept: 'application/json' } });
if (res.ok) {
const body = await res.json();
if (body.HasErrors) throw new Error(`Bazaarvoice returned errors: ${JSON.stringify(body.Errors)}`);
return body;
}
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Bazaarvoice returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
async function send(r, historical) {
const id = `bazaarvoice-${r.Id}`;
const created = toMillis(r.SubmissionTime);
const base = { userId: r.AuthorId ? `bazaarvoice-${r.AuthorId}` : id, sessionId: id, conversationId: id,
platformJson: { rating: r.Rating, productId: r.ProductId, recommended: r.IsRecommended, locale: r.ContentLocale,
syndicated: r.IsSyndicated, featured: r.IsFeatured, moderationStatus: r.ModerationStatus, campaignId: r.CampaignId,
pros: r.Pros, cons: r.Cons } };
const text = [r.Title, r.ReviewText].filter((x) => x?.trim()).join('\n');
if (text) {
await sendToDimensionLabs({ ...base, text, incoming: true, dimensionlabs_timestamp: created },
{ historical: historical || created < Date.now() - HOUR });
}
let last = created;
for (const c of r.ClientResponses ?? []) {
if (!c.Response?.trim()) continue;
last = Math.max(at(c.Date, last), last + 1000);
await sendToDimensionLabs({ ...base, text: c.Response.trim(), incoming: false, dimensionlabs_timestamp: last,
platformJson: { ...base.platformJson, responseType: c.ResponseType, department: c.Department } },
{ historical: historical || last < Date.now() - HOUR });
}
}
// Sends every review changed at or after `since` (Unix ms). Returns the new `since`.
export async function syncBazaarvoice({ since, historical = false }) {
let newest = since, from = since;
for (;;) {
let restart = false;
for (let offset = 0; ; offset += 100) {
if (offset + 100 > MAX_OFFSET) { restart = true; break; } // past Bazaarvoice's offset limit: start again from the newest seen
const q = new URLSearchParams({ apiversion: '5.4', passkey: process.env.BV_PASSKEY,
Filter: `LastModificationTime:gte:${Math.floor(from / 1000)}`, Sort: 'LastModificationTime:asc', Limit: '100', Offset: String(offset) });
const { Results = [] } = await get(`${HOST}/data/reviews.json?${q}`);
for (const r of Results) {
newest = Math.max(newest, at(r.LastModificationTime, newest));
await send(r, historical);
}
if (Results.length < 100) break;
}
if (!restart || newest === from) return newest;
from = newest;
}
}

A review that changes (an edit, a new brand response, a moderation decision) comes back with a newer LastModificationTime and is sent again under the same sessionId. Anything unchanged is a duplicate, and is removed automatically. Comments other shoppers leave on a review are not sent; Bazaarvoice returns them with Include=Comments if you want to add them.

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

  • History first: node run.mjs 2020-01-01 sends every review changed since that date to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends new reviews and responses 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.
  • Rate limits: a big history load can use up your per-minute or hourly allowance. The code waits and retries on 429; check the X-Bazaarvoice-QPM-Allotted header to see your key’s limit.

If Bazaarvoice doesn’t approve the Conversations API for this, use its Displayable Content Export (DCE), which Bazaarvoice recommends for downloading all native and syndicated content and offers, for an added fee, for backend data management. It gives a full data set every Monday and an incremental one on the other days, using a passkey and a shared secret key from Bazaarvoice. Data sets are available about 24 hours after the DCE key is first approved. Convert the files to a CSV with the converter, check it with the file checker, upload it, then run a backfill.

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