02 Get your data in /Integrations
Aircall
Send call and voicemail transcripts from Aircall to Dimension Labs with the universal API.
- A AircallTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in Aircall call transcripts, and voicemail transcripts, turn by turn, as Aircall AI transcribed them. One call becomes one conversation in Dimension Labs. You run it on your side: a small script pulls from the Aircall API and posts each message to the universal API.
What you need
Section titled “What you need”- The Aircall AI package, with AI Assist or AI Assist Pro. Transcripts are part of Aircall’s Conversation Intelligence, and Aircall only returns them to companies that have signed up for that package. Without it there is no transcript to pull. Check your plan with your Aircall account manager.
- An Aircall API ID and API token, from your company’s settings.
- A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Aircall
Section titled “1. Get API access in Aircall”- Open your company’s Settings page in the Aircall Dashboard. In the API Keys section, select Add a new API key.
- Copy the API ID and the API token. Aircall doesn’t store the token in plain text, so it can’t show it to you again.
- Send them with HTTP Basic authentication: the API ID is the username and the API token is the password, as
Authorization: Basic BASE64(api_id:api_token).
OAuth is only for apps built for several Aircall companies; you don’t need it for your own account.
2. Pull new calls
Section titled “2. Pull new calls”List calls, then get each call’s transcription:
curl -u "$AIRCALL_API_ID:$AIRCALL_API_TOKEN" \ "https://api.aircall.io/v1/calls?from=1761436800&to=1761523200&order=asc&per_page=50&fetch_contact=true"
curl -u "$AIRCALL_API_ID:$AIRCALL_API_TOKEN" \ "https://api.aircall.io/v1/calls/CALL_ID/transcription"fromandtofilter calls by when they were created, in UNIX seconds. The code asks one day at a time, fromAIRCALL_LOOKBACK_HOURS(24 by default) before the cursor, and keeps calls that ended since the last run (ended_at, withstatusdone).- Pages hold up to 50 calls (
per_page). Followmeta.next_page_linkuntil it isnull. A single list stops at 10,000 calls even with paging, which is why the code asks one day at a time. - Only six months of call history is available. For older calls, Aircall’s Support team can make a one-time export.
fetch_contact=trueadds the contact to each call. Without it,contactis left out.- The transcription has
type(callorvoicemail) andcontent.utterances[], each withtext,start_time,end_timeandparticipant_type:internalis an Aircall user (your side, with auser_id),externalis the other party (with aphone_number). A call with no transcription returns404. - Use the transcription endpoint, not
realtime_transcription, which Aircall is retiring.
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Aircall | Notes |
|---|---|---|
text | content.utterances[].text | Skip empty utterances. |
userId | call contact.id | Aircall’s id for the contact, with fetch_contact=true. If the call has no contact, the code uses the sessionId. Never the phone number. |
incoming | utterances[].participant_type | true when external. internal (an Aircall user) is your side. |
sessionId | call id | One call is one conversation in Dimension Labs. |
conversationId | call id | Same value as sessionId. |
dimensionlabs_timestamp | call started_at plus utterances[].start_time | started_at is UNIX seconds. In Aircall’s example, start_time is seconds into the call, so the code adds it to started_at. Turns closer than a second apart are spaced to 1,000 ms. |
platformJson | transcription type, content.language, call direction, number.id, tag names | Leave out raw_digits, phone_number, the contact’s details and the user’s name and email: they are phone numbers, names and addresses. |
Personal data. Transcripts hold what people say: names, phone numbers, card and account numbers. Each external utterance carries the caller’s phone_number, so the code never sends it. Redact the text before you send. See PII redaction.
4. Send it
Section titled “4. Send it”// aircall.mjs. Environment: AIRCALL_API_ID, AIRCALL_API_TOKEN, AIRCALL_LOOKBACK_HOURS (default 24), AIRCALL_DELAY_MINUTES (default 30), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;const DAY = 24 * HOUR;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const API = 'https://api.aircall.io/v1';const AUTH = 'Basic ' + Buffer.from(`${process.env.AIRCALL_API_ID}:${process.env.AIRCALL_API_TOKEN}`).toString('base64');const LOOKBACK = Number(process.env.AIRCALL_LOOKBACK_HOURS ?? 24) * HOUR;const DELAY = Number(process.env.AIRCALL_DELAY_MINUTES ?? 30) * 60 * 1000;
// Retries rate limits (429) and server errors (5xx). Returns null on 404 (a call with no transcription).async function get(url) { for (let attempt = 0; ; attempt++) { const res = await fetch(url, { headers: { Authorization: AUTH } }); if (res.ok) return res.json(); if (res.status === 404) return null; if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Aircall returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || Math.min(60, 2 ** attempt)) * 1000); }}
// Calls created in [from, to), one day at a time: a single list stops at 10,000 calls.async function* callsCreated(from, to) { for (let day = from; day < to; day += DAY) { const q = new URLSearchParams({ from: String(Math.floor(day / 1000)), to: String(Math.floor(Math.min(day + DAY, to) / 1000)), order: 'asc', per_page: '50', fetch_contact: 'true', }); let url = `${API}/calls?${q}`; while (url) { const page = await get(url); yield* page?.calls ?? []; url = page?.meta?.next_page_link ?? null; } }}
// Sends every call that ended at or after `since` (Unix ms). Returns the new `since`.export async function syncAircall({ since, historical = false }) { const until = Date.now() - DELAY; // give Aircall AI time to write the transcription if (until <= since) return since; for await (const c of callsCreated(since - LOOKBACK, until)) { if (c.status !== 'done' || !c.ended_at) continue; const ended = toMillis(c.ended_at); if (ended < since || ended >= until) continue; const found = await get(`${API}/calls/${c.id}/transcription`); const t = found?.transcription; if (!t) continue; const sessionId = `aircall-${c.id}`; const userId = c.contact?.id ? `aircall-${c.contact.id}` : sessionId; const start = toMillis(c.started_at); let last = 0; for (const u of t.content?.utterances ?? []) { if (!u.text?.trim()) continue; const ts = Math.max(start + Math.round((u.start_time ?? 0) * 1000), last + 1000); // keep turns at least a second apart last = ts; await sendToDimensionLabs({ text: u.text, userId, incoming: u.participant_type === 'external', sessionId, conversationId: sessionId, dimensionlabs_timestamp: ts, platformJson: { type: t.type, direction: c.direction, language: t.content?.language, numberId: c.number?.id, tags: (c.tags ?? []).map((x) => x.name) }, }, { historical: historical || ts < Date.now() - HOUR }); } } return until;}5. Run it
Section titled “5. Run it”Save the code as aircall.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncAircall as sync } from './aircall.mjs';.
- History first:
node run.mjs 2026-05-01sends every call since that date to/trackhistorical. Then start a backfill from the Backfill tab. The API only reaches back six months. - Then every 15 minutes: the scheduled job sends calls that ended since the last run to
/track, and they are enriched automatically every day. The code waitsAIRCALL_DELAY_MINUTES(30 by default) after a call ends, so its transcription is ready. A call whose transcription isn’t ready by then is skipped, so raise the delay if yours take longer. Turns more than an hour old by then go to/trackhistorical, and need a backfill. - Re-runs are safe: duplicates are removed automatically.
- Rate limits: Aircall allows 120 requests a minute per company. Each call costs one transcription request, so a long history load takes a while. The code waits whenever Aircall returns
429. Aircall Support can raise the limit.
Without the AI package, or for calls older than six months, there is no transcript to pull through the API. You can still upload a file of transcripts you have, check it first with the file checker, then run a backfill.
Aircall reference
Section titled “Aircall reference”- Aircall API reference
- Basic Auth for Aircall customers
- Rate limiting
- Pagination
- List all Calls
- Conversation Intelligence overview
- Retrieve a transcription
Checked against Aircall’s documentation on 2026-10-26.