Skip to content

02 Get your data in /Integrations

Aircall

Send call and voicemail transcripts from Aircall to Dimension Labs with the universal API.

  1. A AircallTickets, 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 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.

  • 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. Open your company’s Settings page in the Aircall Dashboard. In the API Keys section, select Add a new API key.
  2. 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.
  3. 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.

List calls, then get each call’s transcription:

Terminal window
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"
  • from and to filter calls by when they were created, in UNIX seconds. The code asks one day at a time, from AIRCALL_LOOKBACK_HOURS (24 by default) before the cursor, and keeps calls that ended since the last run (ended_at, with status done).
  • Pages hold up to 50 calls (per_page). Follow meta.next_page_link until it is null. 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=true adds the contact to each call. Without it, contact is left out.
  • The transcription has type (call or voicemail) and content.utterances[], each with text, start_time, end_time and participant_type: internal is an Aircall user (your side, with a user_id), external is the other party (with a phone_number). A call with no transcription returns 404.
  • Use the transcription endpoint, not realtime_transcription, which Aircall is retiring.
Dimension LabsAircallNotes
textcontent.utterances[].textSkip empty utterances.
userIdcall contact.idAircall’s id for the contact, with fetch_contact=true. If the call has no contact, the code uses the sessionId. Never the phone number.
incomingutterances[].participant_typetrue when external. internal (an Aircall user) is your side.
sessionIdcall idOne call is one conversation in Dimension Labs.
conversationIdcall idSame value as sessionId.
dimensionlabs_timestampcall started_at plus utterances[].start_timestarted_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.
platformJsontranscription type, content.language, call direction, number.id, tag namesLeave 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.

// 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;
}

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-01 sends 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 waits AIRCALL_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.

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