# Call history and recordings

Read every call across your numbers and channels from one feed, open a call with its transcript, and fetch a fresh recording link.

`GET /v1/calls` is one feed of every call on your numbers: phone and WhatsApp, inbound and outbound, newest first. Each call record carries its duration, end reason, cost, and the recording and transcript when those were on.

## Read from one place, act on the channel

| Surface | Use it to |
| --- | --- |
| `/v1/calls` | Read. List, fetch and get recordings for every call on both channels, with `contactId` and `contactName` when the other party is a known contact. |
| `/v1/voice/calls` | Act on phone calls: place, transfer, end, browser dial, estimate. Its list and get return phone calls only. |
| `/v1/whatsapp/calls` | Act on WhatsApp calls. See [WhatsApp calling](https://pozy.ai/docs/whatsapp/calling). |

## List calls

```bash cURL
curl "https://api.pozy.ai/v1/calls?direction=inbound&status=ended&limit=50" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
let before: string | undefined;
do {
  const query = new URLSearchParams({ limit: "100" });
  if (before) query.set("before", before);
  const res = await fetch(`https://api.pozy.ai/v1/calls?${query}`, {
    headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error);
  for (const c of data.calls) console.log(c.channel, c.direction, c.status, c.durationSeconds, c.contactName);
  before = data.nextCursor ?? undefined;
} while (before);
```
```python Python
import os, requests

params = {"limit": 100}
while True:
    page = requests.get(
        "https://api.pozy.ai/v1/calls",
        headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
        params=params,
        timeout=30,
    ).json()
    for c in page["calls"]:
        print(c["channel"], c["direction"], c["status"], c.get("durationSeconds"))
    if not page["nextCursor"]:
        break
    params["before"] = page["nextCursor"]
```

| Filter | Values |
| --- | --- |
| `channel` | `pstn` (phone network) or `whatsapp` |
| `direction` | `inbound` or `outbound` |
| `status` | `ringing`, `answered`, `ended` or `failed` |
| `number` | Calls involving one of your numbers |
| `search` | Digits from either side of the call |
| `limit` | Default 50, up to 200 |
| `before` | The `nextCursor` from the previous page. It is `null` on the last page. |

List rows leave out the full `transcript`. `lastTranscriptSnippet` is a preview.

## Open one call

`GET /v1/calls/{id}` returns the full record for a call on either channel.

```json
{
  "call": {
    "_id": "66e5f6a7b8c9d0e1f2a3b4c5",
    "channel": "pstn",
    "direction": "inbound",
    "from": "+13125550188",
    "to": "+14155550142",
    "status": "ended",
    "endReason": "hangup",
    "durationSeconds": 184,
    "isVoicemail": false,
    "recordingEnabled": true,
    "transcriptionEnabled": true,
    "transcript": [
      { "text": "Hi, I am calling about order 1234.", "confidence": 0.96, "at": "2026-10-30T09:00:06Z" }
    ],
    "billing": { "billableCostUSD": 0.135, "currency": "USD" },
    "conversationId": "66c3d2ae7b4f6c8d0e1f2a3b"
  }
}
```

| Field | Meaning |
| --- | --- |
| `endReason` | `hangup`, `no_answer`, `rejected` or `error` |
| `hangupCause`, `sipHangupCause` | The raw carrier cause and SIP code behind `endReason`, for example to tell a busy line from a refused destination |
| `isVoicemail` | The call was taken by voicemail |
| `answeredMachine` | With answering-machine detection on, whether a machine picked up |
| `callErrors` | Problems recorded during the call (failed bridge, recording error). The `message` text is for humans and may change, so do not parse it. |
| `billing.billableCostUSD` | What Pozy bills for the call |

## Get the recording

`recordingUrl` on a call record is signed and expires about 10 minutes after it is issued. For a fresh link, call `GET /v1/calls/{id}/recording`. By default it redirects (`302`) to a playable MP3. Add `as=json` to get `{ "url": "..." }` instead.

```bash
curl "https://api.pozy.ai/v1/calls/66e5f6a7b8c9d0e1f2a3b4c5/recording?as=json" \
  -H "Authorization: Bearer $POZY_API_KEY"
```

Fetch the link when someone presses play rather than storing it. If you need to keep recordings, download the file and store it yourself.

## Turn recording and transcription on

Both are off by default. Set them on the number for every call, or per call when you dial out.

| Where | Fields |
| --- | --- |
| Every call on a number | `recordingEnabled`, `transcriptionEnabled`, `transcriptionLanguage` on `POST /v1/phone-numbers/{id}/voice` |
| One outbound call | `recordOverride`, `transcribeOverride`, `transcriptionLanguage` on `POST /v1/voice/calls` |
| One browser call | `recordOverride` on `POST /v1/voice/calls/web/dial` |

Recording never starts silently: on bridged calls a consent notice plays first. A recording exists only if recording was on when the call started. Voicemail messages are recorded whenever voicemail takes a call.

> **Note:**
> 
> Recording and transcription laws differ by country and state. Some places require every party to agree before a call is recorded. The consent notice helps, but you are responsible for using recordings lawfully.

## What it costs

Recording and transcription are billed per minute on top of the call. See the usage table on [Phone numbers](https://pozy.ai/docs/telephony#pricing) or [Pricing](https://pozy.ai/docs/billing).

## Get calls pushed instead of polling

`call.ended` carries the finished call (duration, end reason, cost, and a signed `recordingUrl` with `recordingExpiresAt` when recording was on). The transcript is not in the event: fetch the call record when you need it.

| Event | When it fires |
| --- | --- |
| [`call.ended`](https://pozy.ai/docs/webhooks/events#call-ended) | Call ended. |
| [`call.failed`](https://pozy.ai/docs/webhooks/events#call-failed) | Call failed. |

## Errors

| Status | Cause | Fix |
| --- | --- | --- |
| `404` | The call does not exist for this key, or it has no recording | Check `recordingEnabled` on the call. Recording must be on before the call starts. |
| `502` | The recording lookup failed | Retry |

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/calls`](https://pozy.ai/docs/api/listCalls) | List all calls (unified history) |
| [`GET /v1/calls/{id}`](https://pozy.ai/docs/api/getCall) | Get a call (any channel) |
| [`GET /v1/calls/{id}/recording`](https://pozy.ai/docs/api/getCallRecording) | Get a call recording |
