Skip to content

02 Get your data in /Integrations

Google Customer Experience Insights

Send call and chat transcripts from Google Customer Experience Insights to Dimension Labs with the universal API.

  1. GC Google Customer Experience InsightsTickets, 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 Customer Experience Insights (CX Insights, formerly Contact Center AI Insights) conversations, segment by segment: call transcripts and chat transcripts, with their per-segment sentiment. One CX Insights conversation becomes one conversation in Dimension Labs. You run it on your side: a small script pulls from the Contact Center AI Insights API and posts each message to the universal API. For a large history in one go, you can also export to BigQuery instead.

  • Conversations already in CX Insights, with transcripts. Google now documents the product under Gemini Enterprise for Customer Experience; the API is still the Contact Center AI Insights API (contactcenterinsights.googleapis.com).
  • A service account in the project that holds them, with a role that includes contactcenterinsights.conversations.list.
  • A Dimension Labs API key for a new Universal API source. See Your API key.
  1. Create a service account for the job, and grant it a role with contactcenterinsights.conversations.list on the project. Creating the account works as in the BigQuery guide.
  2. Run the job with that service account as its identity. The code gets its token through Application Default Credentials with the https://www.googleapis.com/auth/cloud-platform scope. For local testing, gcloud auth application-default login uses your own account.
  3. Note your project ID and location. Google’s examples use us-central1, the global instance; if your conversations sit in a regional instance, use that location (see Regionalization).

List conversations changed since your cursor, with the full view:

Terminal window
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" \
"https://contactcenterinsights.googleapis.com/v1/projects/PROJECT_ID/locations/us-central1/conversations?view=FULL&pageSize=100&filter=update_time>\"2026-10-25T00:00:00Z\""
  • view=FULL is required. The list defaults to BASIC, which leaves out the transcript.
  • filter takes the terms on Filter conversations, for example update_time, create_time, start_time, medium="PHONE_CALL" or labels.key:"value". The code uses update_time, so a conversation whose transcript or analysis changes is sent again (the duplicates are removed).
  • pageSize defaults to 100. When the response has nextPageToken, send it as pageToken for the next page.
  • Each conversation has transcript.transcriptSegments[], each with text, segmentParticipant.role, channelTag, messageTime (when provided), words[].startOffset and sentiment.
Dimension LabsCX InsightsNotes
texttranscriptSegments[].textSkip empty segments.
userIdobfuscatedUserIdThe obfuscated user ID your integration sent. If it is empty, the code uses the sessionId.
incomingsegmentParticipant.role, or channelTagtrue when the role is END_USER. HUMAN_AGENT, AUTOMATED_AGENT and ANY_AGENT are your side. Calls without roles: true when channelTag equals callMetadata.customerChannel. A segment with neither is treated as your side, so set roles or channels when you ingest.
sessionIdconversation idThe last part of name.
conversationIdconversation idSame value as sessionId.
dimensionlabs_timestampmessageTime, or startTime plus the first word’s startOffsetmessageTime is set when your integration provided it. Otherwise the code adds the first word’s offset (from the start of the conversation) to startTime. Segments closer than a second apart are spaced to 1,000 ms.
platformJsonmedium, languageCode, agentId, labels, segment role and sentiment.scoreagentId is the opaque id you gave the agent at ingest.

Personal data. Transcripts hold names, phone numbers, card and account numbers. CX Insights can redact transcripts with Sensitive Data Protection when you ingest them; otherwise redact before you send. See PII redaction.

Install Google’s auth library in your project first: npm install google-auth-library.

// google-cx-insights.mjs. Environment: CXI_PROJECT (project ID), CXI_LOCATION (for example us-central1), DL_API_KEY.
// Google credentials come from Application Default Credentials (a service account), as in the BigQuery guide.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
import { GoogleAuth } from 'google-auth-library';
const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform' });
const BASE = `https://contactcenterinsights.googleapis.com/v1/projects/${process.env.CXI_PROJECT}/locations/${process.env.CXI_LOCATION}/conversations`;
// Retries rate limits (429) and server errors (5xx).
async function get(url) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${await auth.getAccessToken()}` } });
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`CX Insights returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
// Sends every conversation updated after `since` (Unix ms). Returns the new `since`.
export async function syncCxInsights({ since, historical = false }) {
let newest = since;
let pageToken;
do {
const q = new URLSearchParams({ filter: `update_time>"${new Date(since - 1).toISOString()}"`, view: 'FULL', pageSize: '100' });
if (pageToken) q.set('pageToken', pageToken);
const page = await get(`${BASE}?${q}`);
for (const c of page.conversations ?? []) {
const id = c.name.split('/').pop();
const sessionId = `google-cx-insights-${id}`.replace(/:/g, '-');
const userId = c.obfuscatedUserId ? `google-cx-insights-${c.obfuscatedUserId}`.replace(/:/g, '-') : sessionId;
const start = toMillis(c.startTime ?? c.createTime);
const customerChannel = c.callMetadata?.customerChannel;
let last = 0;
for (const [i, s] of (c.transcript?.transcriptSegments ?? []).entries()) {
if (!s.text?.trim()) continue;
const role = s.segmentParticipant?.role;
const incoming = role && role !== 'ROLE_UNSPECIFIED' ? role === 'END_USER' : customerChannel != null && s.channelTag === customerChannel;
const offset = s.words?.[0]?.startOffset; // a duration such as "12.5s", from the start of the conversation
const at = s.messageTime ? toMillis(s.messageTime) : offset ? start + Math.round(parseFloat(offset) * 1000) : start + i * 1000;
const ts = Math.max(at, last + 1000); // keep segments at least a second apart
last = ts;
await sendToDimensionLabs({
text: s.text,
userId,
incoming,
sessionId,
conversationId: sessionId,
dimensionlabs_timestamp: ts,
platformJson: { medium: c.medium, language: s.languageCode ?? c.languageCode, agentId: c.agentId, labels: c.labels, role, sentiment: s.sentiment?.score },
}, { historical: historical || ts < Date.now() - HOUR });
}
newest = Math.max(newest, toMillis(c.updateTime));
}
pageToken = page.nextPageToken;
} while (pageToken);
return newest;
}

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

  • History first: node run.mjs 2025-01-01 sends every conversation updated since that date to /trackhistorical. Then start a backfill from the Backfill tab. Conversations can carry an expiry time: anything already deleted is gone.
  • Then every 15 minutes: the scheduled job sends conversations changed since the last run, and they are enriched automatically every day. Turns that are more than an hour old when a conversation reaches CX Insights (often the case for calls ingested after they end) go to /trackhistorical, and need a backfill.
  • Re-runs are safe: duplicates are removed automatically.
  • Rate limits: CX Insights allows 3,000 requests a minute in total. The code waits whenever Google returns 429.

You can also upload a file and check it first with the file checker, then run a backfill.

CX Insights can write conversations and their analysis to a BigQuery table, and the BigQuery guide sends rows from a table to Dimension Labs.

  1. Create the dataset and table first (bq mk --dataset and bq mk --table). The export writes its own schema, and fails if the table doesn’t exist.
  2. POST https://contactcenterinsights.googleapis.com/v1/projects/PROJECT_ID/locations/us-central1/insightsdata:export with bigQueryDestination (projectId, dataset, table) and an optional filter, using the same filter terms as above. The export empties the table first unless you set writeDisposition to WRITE_APPEND. It runs as a long-running operation that you can poll.
  3. Each row is one conversation, with conversationName and a repeated sentences field: sentences.sentence is the text, sentences.participantRole the role (when provided), and sentences.startOffsetNanos or sentences.createTimeNanos the time. In the BigQuery guide’s query, UNNEST(sentences) gives one row per sentence, and the mapping above applies.

The export is limited to 60 requests a minute.

Checked against Google’s Customer Experience Insights documentation on 2026-10-26.