# Calls

> Read call and voicemail history, and place outbound phone calls handled by your AI agent.

Source: https://dev.ody.co/api/calls

Calls and voicemails are activities inside a [conversation](https://dev.ody.co/api/conversations). This resource gives you a flat history across every thread, and lets you **place outbound calls that your AI agent (Astra) handles**.

**`GET /v1/api/calls`**

### List calls

Returns call and voicemail history across all conversations, newest first.

**Scope:** `conversations:read`

**Query parameters**

- `limit` *integer* — 1–200, default 50.

**Returns** `{ calls: [...] }`. Each entry has:

- `id` *uuid* — The activity ID.
- `conversationId` *uuid* — Open it with [Retrieve a conversation](https://dev.ody.co/api/conversations#retrieve-a-conversation) to get the transcript and summary.
- `type` *"call" | "voicemail"*
- `direction` *"inbound" | "outbound"*
- `status` *string | null* — For example `completed`, `missed`, `forwarded`.
- `durationSec` *integer | null*
- `externalE164` *string* — The other party's number.
- `contactName` *string | null*
- `forwardedTo` *string | null* — Where the call was forwarded, if it was.
- `callerName` *string | null* — Caller ID name (CNAM), when available.
- `createdAt` *timestamp*

cURL:

```bash
curl "https://api.ody.co/v1/api/calls?limit=20" \
  -H "Authorization: Bearer $ODY_API_KEY"
```
TypeScript:

```ts
const calls = await ody.calls.list({ limit: 20 });
```
Python:

```python
calls = client.calls.list(limit=20)
```

**`POST /v1/api/calls`**

### Place an AI call

Your **published** AI agent dials `to` from one of your numbers (by default the line connected to the agent), holds the conversation, and hangs up. The finished call lands on the contact's timeline with a recording, transcript and AI summary, exactly like an inbound AI-answered call. Subscribe to [`call.completed`](https://dev.ody.co/webhooks/events#call-completed) and [`call.summary.completed`](https://dev.ody.co/webhooks/events#call-summary-completed) to get the outcome.

**Billing** matches inbound AI calls: **100 AI credits** for a call up to 10 minutes, plus 10 credits for each additional started minute. A call that connects but lasts under 15 seconds costs 10 credits, and unanswered calls are free. (Workspaces not yet moved to the credit price list pay a flat 100 credits per call, with calls under 15 seconds free.) With a **test key** nothing is dialed and the response has `simulated: true`.

**Scope:** `calls:place`

**Body**

- `to` *string* **required** — Destination in E.164.
- `from` *string* — Caller ID: one of your active numbers. Defaults to the agent-connected line.

**Returns** `201` with `{ call: { id, to, from, status }, simulated? }`. `call.id` is the carrier call ID.

**Errors (`409`):** the workspace has no plan, the agent isn't published, there's no active number, or the destination is international and international calling isn't enabled.

cURL:

```bash
curl https://api.ody.co/v1/api/calls \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"+15125550123"}'
```
TypeScript:

```ts
const { call } = await ody.calls.place({ to: "+15125550123" });
```
Python:

```python
res = client.calls.place(to="+15125550123")
```

Response:

```json
{
  "call": {
    "id": "v3:6aL…",
    "to": "+15125550123",
    "from": "+15125550100",
    "status": "queued"
  }
}
```

> **Give the agent a job first:**
What the agent says on an outbound call comes from its configuration. Before you call, set its `jobs` (for example "Confirm tomorrow's appointment and offer to reschedule") with [`PATCH /agent`](https://dev.ody.co/api/agent#update-the-agent), then [publish](https://dev.ody.co/api/agent#publish-the-agent).
