Zernio
Zernio
PlatformsVoice & CallsSetupAI Voice Agents
Integrations
ElevenLabsRetell
Outbound CallsBrowser CallingCall History & Recordings
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Voice & Calls

Call History & Recordings

List every call across your numbers and both channels with GET /v1/calls, open one call, and fetch a fresh recording URL.


When you finish this page you can list every call on your numbers, open one and play its recording. You need at least one call on a voice-enabled number. Read history from /v1/calls, which covers both channels; act on a call (place, transfer, end) through the channel's own surface.

Which surface to use

SurfaceJob
/v1/callsRead. Lists, fetches and pulls recordings for every call on both channels (phone and WhatsApp), with contactId and contactName when the counterparty matches a CRM contact.
/v1/voice/callsWrite for phone (PSTN) calls: place, transfer, end, browser dial, estimate. Its list, get and recording routes return phone calls only, scoped to one number with number.
/v1/whatsapp/callsWrite for WhatsApp calls: place, check permissions, estimate. Its list, get and recording routes are scoped to one accountId.

Read from /v1/calls; write on the channel surface. The channel-scoped reads still exist because they enforce account-scoped access, so use them only when you need that scoping.

List calls

Call GET /v1/calls. It returns every call across all of your numbers and both channels, inbound and outbound, newest first, with no accountId and no request per number. Filter with channel, direction, status, number (calls involving one of your numbers) or search (digits from either side).

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: history } = await zernio.calls.listCalls({ query: { limit: 50 } });
for (const c of history.calls) {
  console.log(c.channel, c.direction, c.status, c.durationSeconds, c.contactName);
}
// Next page: pass history.nextCursor as `before`

Response (200):

{
  "calls": [
    {
      "_id": "66e5f6a7b8c9d0e1f2a3b4c5",
      "channel": "pstn",
      "direction": "outbound",
      "from": "+14155550100",
      "to": "+13105551234",
      "status": "ended",
      "endReason": "hangup",
      "durationSeconds": 184,
      "recordingEnabled": true,
      "recordingUrl": "https://...",
      "lastTranscriptSnippet": null,
      "contactId": null,
      "contactName": null,
      "conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
      "startedAt": "2027-01-01T12:00:00Z",
      "endedAt": "2027-01-01T12:03:04Z"
    }
  ],
  "nextCursor": "2027-01-01T12:00:00.000Z"
}

Pass nextCursor as before for the next page; it is null on the last page. List rows omit transcript; lastTranscriptSnippet is the preview.

Fetch a call and its recording

Call GET /v1/calls/{id} for one call on either channel. It returns the full record, including transcript segments when transcription was on.

curl "https://zernio.com/api/v1/calls/66e5f6a7b8c9d0e1f2a3b4c5" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "call": {
    "_id": "66e5f6a7b8c9d0e1f2a3b4c5",
    "channel": "pstn",
    "direction": "outbound",
    "status": "ended",
    "durationSeconds": 184,
    "recordingEnabled": true,
    "transcriptionEnabled": true,
    "transcript": [
      { "text": "Hi, this is Acme calling about your order.", "confidence": 0.97, "at": "2027-01-01T12:00:06Z" }
    ],
    "billing": { "telnyxSeconds": 184, "billableCostUSD": 0.135 }
  }
}

recordingUrl on a call record is signed and expires about 10 minutes after signing. Call GET /v1/calls/{id}/recording for a fresh one: by default it answers 302 to a playable MP3 URL; pass as=json to get { "url": "..." } instead.

curl "https://zernio.com/api/v1/calls/66e5f6a7b8c9d0e1f2a3b4c5/recording?as=json" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "url": "https://recordings.example.com/66e5f6a7b8c9d0e1f2a3b4c5.mp3?token=..."
}

A recording exists only when recording was enabled on the number or the call at call time.

If it fails

A 404 on GET /v1/calls/{id}/recording means the call does not exist under your key or has no recording:

{
  "error": "Call not found, or no recording is available for this call",
  "type": "not_found"
}

Check recordingEnabled on the call record; recording has to be on before the call starts. A 502 means the recording provider lookup failed; retry. Every error uses the envelope in error handling.

Related

  • List all calls, Get a call and Get a call recording: every field and filter.
  • Setup: turn on recording and transcription.
  • Call webhooks: call.ended pushes each finished call to you instead of polling.
  • WhatsApp Calling: the other channel in this feed.
Was this page helpful?

Browser Calling

Place a call from a web page: mint a WebRTC session on your server, register it in the browser with @telnyx/webrtc, then dial.

SMS

Send and receive SMS and MMS from your numbers with POST /v1/sms/messages, with US carrier registration handled through the API.

On this page

Which surface to useList callsFetch a call and its recordingIf it failsRelated