---
title: "Calls and events"
description: "The call record, its history, transcripts, recordings and voicemail, and the call events — including the full call.ended package."
canonical_url: "https://www.agent-identity.dev/docs/phone/calls-and-events"
markdown_url: "https://www.agent-identity.dev/docs/phone/calls-and-events.md"
last_updated: "2026-10-23"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 1100
  task: "Read an agent's calls, transcripts, recordings and voicemail, and react to call events."
  outcome: "You can list and fetch calls, read the transcript, and handle call.ended with its outcome, transcript and follow-ups."
  appliesTo:
    package:
      - "@aid/api"
      - "@agentidentity/sdk"
  prerequisites:
    - "An org key, or the agent's own key (which sees only its own calls)."
  files:
    - "apps/api/src/services/phone.ts"
  sideEffects:
    - "None for reads. POST /v1/calls/{id}/hangup ends a live call."
  verification:
    - description: "Wait for the call to end."
      expect: "A call.ended event arrives for the call id, with outcome and transcript."
  rollback:
    - "Not applicable."
  failureModes:
    - symptom: "409 phone.conflict call_already_ended on hangup."
      resolution: "The call is already over. Read it with GET /v1/calls/{id} instead."
    - symptom: "404 on GET /v1/calls/{id}/recording."
      resolution: "The call never connected, or it ended moments ago and the recording is still being saved. Wait for call.recording_ready."
    - symptom: "A recording_url stops working."
      resolution: "Links last about 10 minutes. Call GET /v1/calls/{id}/recording again for a new one."
    - symptom: "404 on GET /v1/calls/{id}/voicemail."
      resolution: "The call has no voicemail."
    - symptom: "transcript.abridged is true in call.ended."
      resolution: "The event carries the first 200 lines. Fetch the rest from transcriptUrl."
---

# Calls and events
URL: /docs/phone/calls-and-events
LLM index: /llms.txt
Description: The call record, its history, transcripts, recordings and voicemail, and the call events — including the full call.ended package.
Related: /docs/concepts/events, /docs/concepts/webhooks, /docs/phone/aid-voice

<!-- farming-labs:agent-contract:start -->
## Agent Contract

Task: Read an agent's calls, transcripts, recordings and voicemail, and react to call events.
Outcome: You can list and fetch calls, read the transcript, and handle call.ended with its outcome, transcript and follow-ups.

### Applies To

- Package: `@aid/api`, `@agentidentity/sdk`

### Prerequisites

- An org key, or the agent's own key (which sees only its own calls).

### Files

- `apps/api/src/services/phone.ts`

### Side Effects

- None for reads. POST /v1/calls/{id}/hangup ends a live call.

### Verification

- Wait for the call to end.
  - Expected: A call.ended event arrives for the call id, with outcome and transcript.

### Rollback

- Not applicable.

### Failure Modes

- 409 phone.conflict call_already_ended on hangup. — Recovery: The call is already over. Read it with GET /v1/calls/{id} instead.
- 404 on GET /v1/calls/{id}/recording. — Recovery: The call never connected, or it ended moments ago and the recording is still being saved. Wait for call.recording_ready.
- A recording_url stops working. — Recovery: Links last about 10 minutes. Call GET /v1/calls/{id}/recording again for a new one.
- 404 on GET /v1/calls/{id}/voicemail. — Recovery: The call has no voicemail.
- transcript.abridged is true in call.ended. — Recovery: The event carries the first 200 lines. Fetch the rest from transcriptUrl.
<!-- farming-labs:agent-contract:end -->

# Calls and events

## The call

| Field | Meaning |
| --- | --- |
| `id`, `identity_id` | The call and the agent it belongs to. |
| `direction` | `incoming` or `outgoing`. |
| `from`, `to` | E.164 numbers. |
| `status` | `initiated`, `answered`, `ended`. |
| `mode` | `hosted_agent` (Aid Voice), `client_websocket` (your agent), or `voicemail`. |
| `reason` | The task, for calls placed with Aid Voice. |
| `on_voicemail`, `voicemail_message` | What a placed call does at voicemail. |
| `forwarded_to` | Where a forwarded call was sent. |
| `outcome` | Aid Voice calls only: `completed`, `no_answer`, `declined`, `failed`. |
| `follow_ups` | Open follow-ups Aid Voice recorded. |
| `hangup_reason` | `local` (our side ended it), `remote` (the other party), `rejected` (declined or blocked), `failed`, `voicemail` (voicemail answered), `no_answer` (it rang out). |
| `has_recording` | The call's recording is saved. |
| `hangup_cause`, `duration_seconds`, `is_blocked` | Carrier detail, length, and whether it was refused by [Access](/docs/phone/access). |
| `started_at`, `answered_at`, `ended_at` | When. |

## Read calls

```ts title="calls.ts"
const calls = await self.phone.listCalls({ identityId: agent.id, limit: 50 });
const call = await self.phone.getCall(calls[0].id);
```

| Request | Does |
| --- | --- |
| `GET /v1/identities/{id}/calls` | History, newest first. `limit` (1–200), `offset`, `is_blocked`, `start_datetime`, `end_datetime` (RFC 3339 with an offset). |
| `GET /v1/calls/{id}` | One call. |
| `POST /v1/calls/{id}/hangup` | End a live call. 409 `call_already_ended` once it is over. |
| `GET /v1/calls/{id}/tool-invocations` | What Aid Voice did. See [Aid Voice](/docs/phone/aid-voice#tool-activity). |

## Transcripts

On Aid Voice calls and in text mode, both sides are written for you as the call goes. If you handle raw audio, or want to
add notes, write segments yourself:

```ts title="transcript.ts"
await self.phone.appendTranscript(call.id, [
  { party: "remote", text: "Hello?", tsMs: 800 },
  { party: "local", text: "Hi, this is Sara." },
]);
const transcript = await self.phone.listTranscript(call.id);
```

`party` is `remote` (the other person), `local` (the agent) or `system`. `GET` and `POST /v1/calls/{id}/transcripts`.

## Recording

Every connected call is recorded, the agent and the other side on separate channels, unless the agent has
[recording turned off](/docs/phone/incoming-calls#recording).
`GET /v1/calls/{id}/recording` (`getRecording`) returns an MP3 `recording_url` and `duration_seconds`. The link works
for about 10 minutes; ask again for a new one. It is a 404 until the call has ended and the recording is saved, which
`call.recording_ready` announces.

```ts
const recording = await client.phone.getRecording(callId);
if (recording) console.log(recording.recordingUrl);
```

Recording laws differ by place, and some require everyone on the call to agree. Tell callers when that applies to
you, for example in Aid Voice's greeting.

## Voicemail

`GET /v1/calls/{id}/voicemail` (`getVoicemail`) returns `recording_url`, `duration_seconds` and `created_at` for a
message left on the call, or 404 when there is none.

## Events

Calls write to the same log as mail, so `events.wait` works on them and webhooks deliver them. Every payload has
`callId`, `direction`, `from` and `to`.

| Event | When | Also carries |
| --- | --- | --- |
| `call.started` | A placed call began, or an incoming call was accepted. | |
| `call.answered` | The call connected. | |
| `call.forwarded` | An incoming call was sent on. | `forwardedTo` |
| `call.voicemail_left` | A caller left a message. | `recordingUrl`, `durationSeconds` |
| `call.recording_ready` | The call's recording is saved. | `durationSeconds` |
| `call.ended` | A connected call ended, or any Aid Voice call ended (answered or not). | See below |
| `call.missed` | An incoming call to your agent was never answered. | `hangupReason` |
| `call.failed` | A call placed with your agent never connected. | `hangupReason` |

Declined and blocked calls write no events.

## `call.ended`

One event with the whole story:

```json title="call.ended payload"
{
  "callId": "0193…",
  "direction": "outgoing",
  "from": "+12125550100",
  "to": "+14155550100",
  "outcome": "completed",
  "mode": "hosted_agent",
  "reason": "Book a cleaning next week, mornings preferred.",
  "answered": true,
  "durationSeconds": 94,
  "hangupReason": "local",
  "hangupCause": "normal_clearing",
  "hangupSource": "caller",
  "forwardedTo": null,
  "transcript": {
    "entries": [{ "party": "local", "text": "Hi, this is Ava from…", "tsMs": 1200 }],
    "abridged": false
  },
  "transcriptUrl": "https://api.agent-identity.dev/v1/calls/0193…/transcripts",
  "followUps": [{ "id": "0193…", "seq": 1, "action": "Confirm Tuesday 9:30am", "details": null, "status": "open" }]
}
```

- `outcome`: on Aid Voice calls, `completed` (connected and ended normally, including at voicemail), `no_answer`,
  `declined` (busy or rejected) or `failed`. On calls run by your agent it is `completed` when the call connected.
- `transcript.entries` holds the first 200 lines; `abridged` is `true` when there are more. `transcriptUrl` reads all
  of them.
- Aid Voice calls send `call.ended` even when nobody answered, so a task never fails silently.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
