02 Get your data in /Integrations
Vonage Contact Center
Send call transcripts from Vonage Contact Center to Dimension Labs with the universal API.
- VC Vonage Contact CenterTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in Vonage Contact Center call transcripts from Conversation Analyzer, one call as one message. One call becomes one conversation in Dimension Labs. You run it on your side: a small script pulls from the Vonage Contact Center (VCC) APIs and posts each transcript to the universal API.
The transcript the API returns is one block of text with no speakers, so each call is sent as a single message, as described in Transcripts in one block. Dimensions are still extracted from the whole call. What you can’t do is separate what the customer said from what your agent said.
What you need
Section titled “What you need”- Conversation Analyzer on your VCC account. It is a provisioned feature: if you don’t have it, ask Vonage. Call recording must be on too.
- Calls set up for analysis. Vonage lists these settings:
- In User Admin > Agent Settings, set Transcribe calls to Yes for each agent whose calls you want.
- Your interaction plan sets a language and a Conversation Analyzer profile on each call. Without both, Conversation Analyzer doesn’t analyze the call.
- API credentials with the
interaction-content:readscope, created by a user who can see the calls: View inbound interaction content, View outbound interaction content, and access to the groups of the agents who took the calls. A token has the permissions of the user who created the credentials. - A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Vonage Contact Center
Section titled “1. Get API access in Vonage Contact Center”Vonage’s steps are in How to authenticate with a Vonage Contact Center (VCC) API.
- Log in to the VCC Admin Portal and go to API Credentials. If you don’t see it, contact VCC support.
- Click Create New. Give the credentials a name, and under Scopes add
interaction-content:read. - Click Save API credential, and copy the client ID and secret. You can’t see the secret again.
- Find your region:
emea,nam(USA) orapac. The code takes it asVONAGE_REGION. - Get a token:
POST https://YOUR_REGION.cc.vonage.com/Auth/connect/tokenwith a form body ofgrant_type=client_credentials,client_id,client_secretandscope=interaction-content:read. Send it asAuthorization: Bearer YOUR_TOKEN. Vonage says a token lasts 15 minutes by default, so the code gets a new one when a request returns401.
Every request also needs a version in the Accept header. The code uses application/vnd.newvoicemedia.v3+json.
2. Pull new transcripts
Section titled “2. Pull new transcripts”Find calls that have a transcript with the Interaction Content API, then get their transcripts from the Conversation Analyzer API, up to 50 calls at a time:
curl -H "Authorization: Bearer $VCC_TOKEN" -H "Accept: application/vnd.newvoicemedia.v3+json" \ "https://nam.api.cc.vonage.com/interaction-content/interactions?start=2026-10-20T00:00:00Z&end=2026-10-26T00:00:00Z"
curl -H "Authorization: Bearer $VCC_TOKEN" -H "Accept: application/vnd.newvoicemedia.v3+json" \ "https://nam.api.cc.vonage.com/conversation-analyzer/interactions?calls=GUID_1,GUID_2&transcript=true"startandendare ISO 8601, and a search can span at most 7 days. The code searches 6 days at a time, fromVONAGE_LOOKBACK_DAYSbefore the cursor, and keeps calls whose transcript changed since the last run.- The search only returns interactions that have at least one content file. Each item has
guid,start,direction,handlingAgentsandcontent[]. A call with a transcript has a content item withtypetranscript, and itslastModifiedis the code’s cursor. limitsets the page size (1 to 1,000, 1,000 by default). Whenmeta.nextContinuationTokenis set, send it ascontinuationTokenfor the next page.callstakes up to 50 comma-separated GUIDs. Withtranscript=true, each item hastranscript, a single string of the call’s text with no speaker labels. It also hasstatus(processing,processedorfailed),language,confidence,categories,callTimeBreakDown(seconds of agent talk, customer talk, silence and cross talk) and, when sentiment is on,sentimentSummary.
About speakers. Conversation Analyzer separates the speakers on its own screen, but the API’s transcript doesn’t: Vonage’s example is "Case.\nHi, and yeah, just a call here. Thank you bye.". The Interaction Content API can also return the transcript as a JSON file (GET /interaction-content/interactions/{guid}/content/transcript), but Vonage doesn’t document that file’s layout. If you download one and it does label each line with a speaker, you can split it into turns instead: see Transcripts in one block.
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Vonage Contact Center | Notes |
|---|---|---|
text | transcript | The whole call, with line breaks turned into spaces. Calls with an empty transcript are skipped. |
userId | guid | The API has no customer id: connectFrom and connectTo are phone numbers. The code uses the sessionId, or look up your own internal customer id in your system. |
incoming | none | Always true: the transcript holds the customer’s words, and there is nothing to split it on. |
sessionId | guid | One call is one conversation in Dimension Labs. |
conversationId | guid | Same value as sessionId. |
dimensionlabs_timestamp | start | When the call started. |
platformJson | direction, language, confidence, callTimeBreakDown, category names, sentimentSummary.overallSentiment | Marked transcript: 'unsplit' so you can filter these records. Leave out connectFrom and connectTo: they are phone numbers. |
Personal data. Call transcripts hold names, phone numbers, card and account numbers, and addresses. Vonage has a personal data redaction feature for call recordings and transcripts (a provisioned feature, for English language profiles); use it, or redact before you send. See PII redaction.
4. Send it
Section titled “4. Send it”// vonage-contact-center.mjs. Environment: VONAGE_REGION (emea, nam or apac), VONAGE_CLIENT_ID, VONAGE_CLIENT_SECRET,// VONAGE_LOOKBACK_DAYS (default 2), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const DAY = 24 * HOUR;const WINDOW = 6 * DAY; // a search can span at most 7 daysconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));const REGION = process.env.VONAGE_REGION ?? 'nam';const API = `https://${REGION}.api.cc.vonage.com`;const LOOKBACK = Number(process.env.VONAGE_LOOKBACK_DAYS ?? 2) * DAY;let token;
async function newToken() { const body = new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.VONAGE_CLIENT_ID, client_secret: process.env.VONAGE_CLIENT_SECRET, scope: 'interaction-content:read', }); const res = await fetch(`https://${REGION}.cc.vonage.com/Auth/connect/token`, { method: 'POST', body }); if (!res.ok) throw new Error(`Vonage token request returned ${res.status}: ${await res.text()}`); token = (await res.json()).access_token;}
// Gets a fresh token on 401, and retries rate limits (429) and server errors (5xx). Returns null on 404.async function get(path) { for (let attempt = 0; ; attempt++) { if (!token) await newToken(); const res = await fetch(`${API}${path}`, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.newvoicemedia.v3+json' }, }); if (res.ok) return res.json(); if (res.status === 404) return null; if (res.status === 401 && attempt === 0) { token = null; continue; } if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Vonage returned ${res.status} for ${path}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
// Calls whose transcript changed between `since` and `until`, with that time as `changed`.async function* callsWithNewTranscripts(since, until) { for (let from = since - LOOKBACK; from < until; from += WINDOW) { const to = Math.min(from + WINDOW, until); let continuationToken; do { const q = new URLSearchParams({ start: new Date(from).toISOString(), end: new Date(to).toISOString(), limit: '1000' }); if (continuationToken) q.set('continuationToken', continuationToken); const page = await get(`/interaction-content/interactions?${q}`); for (const item of page?.items ?? []) { const t = (item.content ?? []).find((c) => c.type === 'transcript'); const changed = t?.lastModified ? toMillis(t.lastModified) : null; if (changed !== null && changed >= since && changed < until) yield { ...item, changed }; } continuationToken = page?.meta?.nextContinuationToken; } while (continuationToken); }}
async function sendBatch(batch, historical) { let held = Infinity; // the earliest call still being processed: the cursor stays before it const found = await get(`/conversation-analyzer/interactions?calls=${batch.map((c) => c.guid).join(',')}&transcript=true`); const byGuid = new Map((Array.isArray(found) ? found : []).map((r) => [r.guid, r])); for (const call of batch) { const r = byGuid.get(call.guid); if (r?.status === 'processing') { held = Math.min(held, call.changed); continue; } const text = (r?.transcript ?? '').replace(/\s+/g, ' ').trim(); if (r?.status !== 'processed' || !text) continue; const sessionId = `vonage-${call.guid}`.replace(/:/g, '-'); const ts = toMillis(call.start); await sendToDimensionLabs({ text, userId: sessionId, // no customer id in the API; connectFrom is a phone number incoming: true, // it holds the customer's words sessionId, conversationId: sessionId, dimensionlabs_timestamp: ts, platformJson: { transcript: 'unsplit', channel: 'voice', direction: call.direction, language: r.language, confidence: r.confidence, callTimeBreakDown: r.callTimeBreakDown ?? r.callTimeBreakdown, categories: (r.categories ?? []).map((c) => c.category), sentiment: r.sentimentSummary?.overallSentiment, }, }, { historical: historical || ts < Date.now() - HOUR }); } return held;}
// Sends every call transcript that appeared or changed at or after `since` (Unix ms). Returns the new `since`.export async function syncVonage({ since, historical = false }) { token = null; // a new token for each run const until = Date.now(); let newest = since; let held = Infinity; let batch = []; for await (const call of callsWithNewTranscripts(since, until)) { batch.push(call); newest = Math.max(newest, call.changed); if (batch.length === 50) { held = Math.min(held, await sendBatch(batch, historical)); batch = []; } } if (batch.length) held = Math.min(held, await sendBatch(batch, historical)); return Math.min(newest, held);}A call that Conversation Analyzer is still processing keeps the cursor where it is, so the next run asks for it again. Calls already sent in between come back too, which is fine: duplicates are removed automatically.
This guide covers calls. Chat transcripts are content files too (type chatTranscript, fetched by their contentKey), but Vonage doesn’t document their layout. If you want them, download one and check it against Transcripts in one block.
5. Run it
Section titled “5. Run it”Save the code as vonage-contact-center.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncVonage as sync } from './vonage-contact-center.mjs';.
- History first:
node run.mjs 2025-01-01sends every call transcript since that date to/trackhistorical. Then start a backfill from the Backfill tab. Only calls whose content your retention settings still keep have a transcript. - Then every 15 minutes: the scheduled job sends new transcripts to
/track, and they are enriched automatically every day. Conversation Analyzer processes a call after it ends, so a transcript can arrive more than an hour after the call started: those go to/trackhistorical, and need a backfill. The search looks backVONAGE_LOOKBACK_DAYS(2 by default) for calls whose transcript is new; raise it if your transcripts take longer. - Re-runs are safe: duplicates are removed automatically.
- Rate limits: the code waits whenever Vonage returns
429.
Vonage also sends an interaction.transcriptavailable.v1 webhook event when a transcript is ready, if you’d rather react to events than poll. You can also upload a file and check it first with the file checker, then run a backfill.
Vonage Contact Center reference
Section titled “Vonage Contact Center reference”- How to authenticate with a Vonage Contact Center (VCC) API
- Search for interactions (Interaction Content API)
- Get single interaction content file
- Get multiple interactions (Conversation Analyzer API)
- Get a single interaction (Conversation Analyzer API)
- Versioning (Conversation Analyzer API)
- Configuring permissions for accessing interaction content
- interaction.transcriptavailable.v1
- Conversation Analyzer
- Personal data redaction in interaction recordings
Checked against Vonage Contact Center’s documentation on 2026-10-26.