---
title: "Incoming calls"
description: "Choose what happens when someone calls an agent — Aid Voice, your agent, your server, forward or decline — plus the voice and voicemail."
canonical_url: "https://www.agent-identity.dev/docs/phone/incoming-calls"
markdown_url: "https://www.agent-identity.dev/docs/phone/incoming-calls.md"
last_updated: "2026-10-23"
x_farming_labs_generated_preamble: true
agent:
  tokenBudget: 1100
  task: "Set what happens to calls that arrive on an agent's number."
  outcome: "PUT /v1/identities/{id}/incoming-call-action returns the saved setting, and the next incoming call follows it."
  appliesTo:
    package:
      - "@aid/api"
      - "@agentidentity/sdk"
  prerequisites:
    - "The agent has a phone number."
    - "An org key, or the agent's own key."
  files:
    - "apps/api/src/services/phone.ts"
  sideEffects:
    - "Forwarded calls use the organization's call minutes while connected."
  verification:
    - description: "Read the setting back."
      expect: "GET /v1/identities/{id}/incoming-call-action shows incoming_call_action and the URLs or forwarding target saved with it."
  rollback:
    - "PUT the previous action again. URLs not named in a request stay as they were saved."
  failureModes:
    - symptom: "400: client_websocket_url is required when the action is auto_accept."
      resolution: "Your agent needs a wss:// address. Send client_websocket_url with the action."
    - symptom: "400: forwarding_phone_number or forwarding_sip_uri is required when the action is forward."
      resolution: "Send one destination: an E.164 number or a sip:user@host address."
    - symptom: "Calls to the webhook action are always refused."
      resolution: "Your server must answer within 5 seconds with a 2xx and {\"action\":\"answer\"}. Anything else, including a timeout, declines the call."
---

# Incoming calls
URL: /docs/phone/incoming-calls
LLM index: /llms.txt
Description: Choose what happens when someone calls an agent — Aid Voice, your agent, your server, forward or decline — plus the voice and voicemail.
Related: /docs/phone/aid-voice, /docs/phone/your-agent, /docs/phone/access, /docs/concepts/webhooks

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

Task: Set what happens to calls that arrive on an agent's number.
Outcome: PUT /v1/identities/{id}/incoming-call-action returns the saved setting, and the next incoming call follows it.

### Applies To

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

### Prerequisites

- The agent has a phone number.
- An org key, or the agent's own key.

### Files

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

### Side Effects

- Forwarded calls use the organization's call minutes while connected.

### Verification

- Read the setting back.
  - Expected: GET /v1/identities/{id}/incoming-call-action shows incoming_call_action and the URLs or forwarding target saved with it.

### Rollback

- PUT the previous action again. URLs not named in a request stay as they were saved.

### Failure Modes

- 400: client_websocket_url is required when the action is auto_accept. — Recovery: Your agent needs a wss:// address. Send client_websocket_url with the action.
- 400: forwarding_phone_number or forwarding_sip_uri is required when the action is forward. — Recovery: Send one destination: an E.164 number or a sip:user@host address.
- Calls to the webhook action are always refused. — Recovery: Your server must answer within 5 seconds with a 2xx and {"action":"answer"}. Anything else, including a timeout, declines the call.
<!-- farming-labs:agent-contract:end -->

# Incoming calls

Each agent has one setting for calls that arrive on its number. New numbers answer with Aid Voice.

| Option | `incoming_call_action` | Needs |
| --- | --- | --- |
| Aid Voice | `hosted_agent` | Nothing. Optional [instructions](/docs/phone/aid-voice). |
| Your agent | `auto_accept` | `client_websocket_url` (`wss://`) |
| Your server | `webhook` | `incoming_call_webhook_url` (`https://`) |
| Forward | `forward` | `forwarding_phone_number` or `forwarding_sip_uri` |
| Decline | `auto_reject` | Nothing |

Blocked callers never reach any of these: the call is refused before it connects. See [Access](/docs/phone/access).

```ts title="incoming.ts"
await org.phone.setIncomingCallAction(agent.id, {
  incomingCallAction: "auto_accept",
  clientWebsocketUrl: "wss://agent.example.com/stream",
});
```

```bash title="terminal"
curl -sS -X PUT "https://api.agent-identity.dev/v1/identities/$AID_AGENT/incoming-call-action" \
  -H "authorization: Bearer $AID_KEY" -H 'content-type: application/json' \
  -d '{"incoming_call_action":"auto_accept","client_websocket_url":"wss://agent.example.com/stream"}'
```

Fields you leave out keep the value already saved, so switching the action does not lose your addresses, voice or
forwarding target.

## Aid Voice

`hosted_agent`. Our voice agent answers, using the agent's standing instructions and greeting. No server needed. See
[Aid Voice](/docs/phone/aid-voice).

## Your agent

`auto_accept`. We answer and open a WebSocket to your `client_websocket_url`, then send the conversation as text. See
[Your agent](/docs/phone/your-agent) for the protocol.

## Your server

`webhook`. We POST the call to `incoming_call_webhook_url` and wait up to 5 seconds. Answer with JSON:

```json title="your reply"
{ "action": "answer", "client_websocket_url": "wss://agent.example.com/stream" }
```

`client_websocket_url` is optional; leave it out to use the one saved on the agent. Reply `{"action":"reject"}` to
decline. A slow reply, a non-2xx status or anything else declines the call.

```json title="what we send"
{
  "id": "0193…",
  "origin": "dedicated_number",
  "local_phone_number": "+12125550100",
  "remote_phone_number": "+14155550999",
  "direction": "inbound",
  "status": "initiated",
  "client_websocket_url": "wss://agent.example.com/stream",
  "created_at": "2026-10-09T14:02:11.000Z",
  "rate_limit": { "calls_used": 4, "calls_remaining": 96, "calls_limit": 100, "minutes_used": 12.5, "minutes_remaining": 287.5, "minutes_limit": 300 }
}
```

The request is signed like our other webhooks, with `x-aid-request-id`, `x-aid-timestamp` and `x-aid-signature`
(`v1=<hex HMAC-SHA256 of "{request-id}.{timestamp}.{body}">`). The secret belongs to the agent and is returned in
`signing_secret` when the action is `webhook`, so `verifyWebhook()` from the SDK works unchanged.

## Forward calls to your phone

`forward`. We send the call on to one destination, without opening a WebSocket. The caller ID the destination sees is
the agent's number.

```ts title="forward.ts"
await org.phone.setIncomingCallAction(agent.id, {
  incomingCallAction: "forward",
  forwardingPhoneNumber: "+14155550123",
});
```

- `forwarding_phone_number`: E.164, such as `+14155550123`.
- `forwarding_sip_uri`: `sip:user@host` with an optional `:port`. The host must be a domain name, not an IP address.
  Parameters and credentials are not accepted.

Send one or the other, not both. Setting one clears the other. The call is marked with `forwarded_to` and a
`call.forwarded` event is written.

## Decline

`auto_reject`. The call is refused, unless the agent takes voicemail (below), in which case it is answered and recorded.

## The voice

The voice is part of the same setting and applies to Aid Voice and to text sent by your agent.

```ts title="voice.ts"
const { voices, defaultVoice } = await org.phone.listVoices();

await org.phone.setIncomingCallAction(agent.id, {
  incomingCallAction: "hosted_agent",
  voice: "AWS.Polly.Amy-Neural",
  language: "en-GB",
});
```

`GET /v1/phone/voices` lists every voice with its `id`, `name`, `description` and `language`. Save the voice `id` as
`voice` and its `language` as `language`. `null` returns to the server's default.

## Speech to text

`transcription_engine` (`transcriptionEngine`) picks what turns the other side's speech into text, on calls the agent
answers and calls it places: `deepgram` (Deepgram Nova-3, the default), `google`, or `telnyx` (Whisper). It is saved
with the same setting, and `null` returns to the default. Whisper sends no partial results, so the agent hears a
sentence only once it ends and cannot be interrupted mid-sentence.

```ts title="speech-to-text.ts"
await org.phone.setIncomingCallAction(agent.id, {
  incomingCallAction: "hosted_agent",
  transcriptionEngine: "google",
});
```

## Recording

Every connected call is recorded, each side on its own channel, and kept with the call (see
[Calls and events](/docs/phone/calls-and-events#recording)). `record_calls: false` (`recordCalls`) turns it off for
one agent, on calls it answers and calls it places; `true` turns it back on.

## Voicemail

Voicemail is saved with the [Aid Voice settings](/docs/phone/aid-voice) and applies whatever the action is:

| `voicemail_mode` | When the call would be declined |
| --- | --- |
| `leave_message` | Answer, speak `voicemail_greeting`, and record up to `voicemail_max_seconds` (default 120). |
| `hang_up` | Decline. The default. |
| `ignore` | Do nothing: the call keeps ringing. |

With **Your agent**, if your server cannot be reached when a call is answered and `voicemail_mode` is `leave_message`,
the caller is sent to voicemail instead of hearing silence. A left message writes a `call.voicemail_left` event and is
readable at `GET /v1/calls/{id}/voicemail`.

## Reference

| Request | Does |
| --- | --- |
| `GET /v1/identities/{id}/incoming-call-action` | Read the setting. An agent with none saved reads as `auto_reject`. |
| `PUT /v1/identities/{id}/incoming-call-action` | Save it. `incoming_call_action` is required; everything else is optional. |
| `GET /v1/phone/voices` | The voice catalog and `default_voice`. |

## 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).
