List all calls (unified history)
Unified call history across ALL of your numbers: both channels
(WhatsApp Business Calling + regular phone/PSTN), inbound and outbound,
newest first. Unlike GET /v1/voice/calls (PSTN-only) and
GET /v1/whatsapp/calls (one account at a time), this endpoint needs no
accountId and never requires fanning out one request per number.
Any row can be opened channel-agnostically via GET /v1/calls/{id} and
GET /v1/calls/{id}/recording; no branching on channel needed. When
the counterparty number matches a CRM contact, contactId and
contactName are set.
Cursor pagination: pass the returned nextCursor as before to fetch
the next page. nextCursor is null on the last page.
API key authentication - use your Zernio API key as a Bearer token
In: header
Query Parameters
"whatsapp" | "pstn""ringing" | "answered" | "ended" | "failed""inbound" | "outbound"Exact filter: calls involving this number (typically one of YOUR numbers, to scope history to a single line). E.164, leading + optional.
Free-text match on the from/to numbers. Non-digits are stripped, so partial queries like 302 or +1 302 work.
Return calls with startedAt strictly before this instant (use the previous page's nextCursor).
date-time501 <= value <= 200Response Body
application/json
application/json
import Zernio from '@zernio/node';const zernio = new Zernio({ apiKey: process.env.ZERNIO_API_KEY });const { data } = await zernio.calls.listCalls();console.log(data);{
"calls": [
{
"_id": "string",
"accountId": "string",
"conversationId": "string",
"contactId": "string",
"channel": "whatsapp",
"direction": "inbound",
"from": "string",
"to": "string",
"forwardTo": "string",
"greeting": "string",
"status": "ringing",
"isVoicemail": true,
"amd": true,
"answeredMachine": true,
"forwardCallerId": "business",
"recordingEnabled": true,
"transcriptionEnabled": true,
"transcriptionLanguage": "auto",
"startedAt": "2019-08-24T14:15:22Z",
"answeredAt": "2019-08-24T14:15:22Z",
"endedAt": "2019-08-24T14:15:22Z",
"transferredAt": "2019-08-24T14:15:22Z",
"durationSeconds": 0,
"endReason": "hangup",
"hangupCause": "string",
"sipHangupCause": "string",
"callErrors": [
{
"code": 0,
"message": "string"
}
],
"recordingUrl": "string",
"lastTranscriptSnippet": "string",
"transcript": [
{
"text": "string",
"confidence": 0,
"at": "2019-08-24T14:15:22Z"
}
],
"billing": {
"metaMinutes": 0,
"telnyxSeconds": 0,
"transcriptionSeconds": 0,
"transcriptionCostUSD": 0,
"metaCostUSD": 0,
"telnyxCostUSD": 0,
"recordingCostUSD": 0,
"billableCostUSD": 0,
"totalCostUSD": 0,
"currency": "string"
},
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z",
"contactName": "string"
}
],
"nextCursor": "2019-08-24T14:15:22Z"
}{
"error": "Unauthorized"
}View a KYC document on file GET
Stream a document backing a reusable verification (the `documentId` values from GET /v1/phone-numbers/kyc `reusable.options[].details[]`), so the account holder can see what's on file before reusing it. Returned inline as `application/pdf` (uploads are normalized to PDF). Auth-scoped: a document is viewable only when its id is referenced by one of the caller's own numbers — otherwise `404`.
Get a call recording GET
Channel-agnostic recording fetch: resolves a fresh, playable MP3 URL for any call regardless of channel (provider-signed URLs expire ~10 minutes after signing, so this re-signs on demand). Default responds `302 Found` redirecting to the fresh URL; pass `as=json` to receive `{ url }` instead.