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.
- GC Google Customer Experience InsightsTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- 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.
What you need
Section titled “What you need”- 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. Get API access in Google Cloud
Section titled “1. Get API access in Google Cloud”- Create a service account for the job, and grant it a role with
contactcenterinsights.conversations.liston the project. Creating the account works as in the BigQuery guide. - 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-platformscope. For local testing,gcloud auth application-default loginuses your own account. - 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).
2. Pull new conversations
Section titled “2. Pull new conversations”List conversations changed since your cursor, with the full view:
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=FULLis required. The list defaults toBASIC, which leaves out the transcript.filtertakes the terms on Filter conversations, for exampleupdate_time,create_time,start_time,medium="PHONE_CALL"orlabels.key:"value". The code usesupdate_time, so a conversation whose transcript or analysis changes is sent again (the duplicates are removed).pageSizedefaults to 100. When the response hasnextPageToken, send it aspageTokenfor the next page.- Each conversation has
transcript.transcriptSegments[], each withtext,segmentParticipant.role,channelTag,messageTime(when provided),words[].startOffsetandsentiment.
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | CX Insights | Notes |
|---|---|---|
text | transcriptSegments[].text | Skip empty segments. |
userId | obfuscatedUserId | The obfuscated user ID your integration sent. If it is empty, the code uses the sessionId. |
incoming | segmentParticipant.role, or channelTag | true 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. |
sessionId | conversation id | The last part of name. |
conversationId | conversation id | Same value as sessionId. |
dimensionlabs_timestamp | messageTime, or startTime plus the first word’s startOffset | messageTime 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. |
platformJson | medium, languageCode, agentId, labels, segment role and sentiment.score | agentId 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.
4. Send it
Section titled “4. Send it”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;}5. Run it
Section titled “5. Run it”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-01sends 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.
Export to BigQuery
Section titled “Export to BigQuery”CX Insights can write conversations and their analysis to a BigQuery table, and the BigQuery guide sends rows from a table to Dimension Labs.
- Create the dataset and table first (
bq mk --datasetandbq mk --table). The export writes its own schema, and fails if the table doesn’t exist. POST https://contactcenterinsights.googleapis.com/v1/projects/PROJECT_ID/locations/us-central1/insightsdata:exportwithbigQueryDestination(projectId,dataset,table) and an optionalfilter, using the same filter terms as above. The export empties the table first unless you setwriteDispositiontoWRITE_APPEND. It runs as a long-running operation that you can poll.- Each row is one conversation, with
conversationNameand a repeatedsentencesfield:sentences.sentenceis the text,sentences.participantRolethe role (when provided), andsentences.startOffsetNanosorsentences.createTimeNanosthe 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.
CX Insights reference
Section titled “CX Insights reference”- Filter conversations
- List conversations
- Export conversations to BigQuery
- BigQuery export schemas
- Quotas and limits
- Enable Customer Experience Insights
- Contact Center AI Insights API discovery document
Checked against Google’s Customer Experience Insights documentation on 2026-10-26.