> ## Documentation Index
> Fetch the complete documentation index at: https://docs.truedy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Call Management

> Monitor active calls, retrieve transcripts, and access recordings

After calls complete, Truedy stores a full record of every conversation — including the transcript, recording audio, duration, cost, and post-call analysis outcome. This guide covers how to query your call history, work with transcripts, access recordings, and export data for downstream systems.

***

## Listing Calls

Use `GET /calls` to retrieve a paginated list of calls across your account. You can filter by agent, status, direction, or date range.

### Endpoint

```
GET https://api.truedy.ai/api/public/v1/calls
```

### Query Parameters

| Parameter        | Type    | Description                                                                               |
| ---------------- | ------- | ----------------------------------------------------------------------------------------- |
| `agent_id`       | string  | Filter to calls handled by a specific agent                                               |
| `status`         | string  | Filter by call status: `queued`, `ringing`, `in-progress`, `ended`, `failed`, `cancelled` |
| `direction`      | string  | Filter by direction: `outbound`, `inbound`, `webrtc`                                      |
| `created_after`  | string  | ISO 8601 datetime — return only calls created after this time                             |
| `created_before` | string  | ISO 8601 datetime — return only calls created before this time                            |
| `limit`          | integer | Number of records per page. Default: `20`. Maximum: `100`                                 |
| `offset`         | integer | Number of records to skip for pagination. Default: `0`                                    |

### Response Structure

```json theme={null}
{
  "data": [
    { /* call object */ },
    { /* call object */ }
  ],
  "meta": {
    "total": 847,
    "limit": 20,
    "offset": 0
  }
}
```

The `meta.total` field gives the total number of calls matching your filters — use this with `limit` and `offset` to paginate through all results.

***

## Getting a Single Call

Use `GET /calls/{id}` to fetch the complete record for one call by its ID.

### Endpoint

```
GET https://api.truedy.ai/api/public/v1/calls/{call_id}
```

***

## Code Examples

<CodeGroup>
  ```bash curl — List calls theme={null}
  # List the 20 most recent ended calls for a specific agent
  curl "https://api.truedy.ai/api/public/v1/calls?agent_id=agent_01hx2a7b3cd4ef5gh6ij7kl8mn&status=ended&limit=20" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash curl — Get single call theme={null}
  curl https://api.truedy.ai/api/public/v1/calls/call_01hx3k9p2qw7v8n4m6j5r0y1tz \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```bash curl — Filter by date range theme={null}
  curl "https://api.truedy.ai/api/public/v1/calls?created_after=2024-04-01T00:00:00Z&created_before=2024-04-08T00:00:00Z&limit=100" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```python Python — List calls theme={null}
  import requests

  def list_calls(agent_id=None, status=None, direction=None,
                 created_after=None, created_before=None,
                 limit=20, offset=0):
      params = {k: v for k, v in {
          "agent_id": agent_id,
          "status": status,
          "direction": direction,
          "created_after": created_after,
          "created_before": created_before,
          "limit": limit,
          "offset": offset,
      }.items() if v is not None}

      response = requests.get(
          "https://api.truedy.ai/api/public/v1/calls",
          headers={"Authorization": "Bearer YOUR_API_KEY"},
          params=params,
      )
      response.raise_for_status()
      return response.json()

  # Example: all outbound calls from April
  result = list_calls(
      direction="outbound",
      created_after="2024-04-01T00:00:00Z",
      created_before="2024-05-01T00:00:00Z",
      limit=100,
  )

  print(f"Total calls: {result['meta']['total']}")
  for call in result["data"]:
      print(f"{call['id']} — {call['status']} — {call['duration_seconds']}s")
  ```

  ```python Python — Get single call + transcript theme={null}
  import requests

  def get_call(call_id):
      response = requests.get(
          f"https://api.truedy.ai/api/public/v1/calls/{call_id}",
          headers={"Authorization": "Bearer YOUR_API_KEY"},
      )
      response.raise_for_status()
      return response.json()

  call = get_call("call_01hx3k9p2qw7v8n4m6j5r0y1tz")

  # Print the full transcript
  if call["transcript"]:
      for turn in call["transcript"]:
          speaker = "Agent" if turn["speaker"] == "agent" else "Contact"
          print(f"[{turn['timestamp']:.1f}s] {speaker}: {turn['text']}")
  ```

  ```javascript JavaScript — List calls theme={null}
  async function listCalls({
    agentId,
    status,
    direction,
    createdAfter,
    createdBefore,
    limit = 20,
    offset = 0,
  } = {}) {
    const params = new URLSearchParams();
    if (agentId) params.set("agent_id", agentId);
    if (status) params.set("status", status);
    if (direction) params.set("direction", direction);
    if (createdAfter) params.set("created_after", createdAfter);
    if (createdBefore) params.set("created_before", createdBefore);
    params.set("limit", limit);
    params.set("offset", offset);

    const response = await fetch(
      `https://api.truedy.ai/api/public/v1/calls?${params}`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } }
    );

    if (!response.ok) throw new Error(`API error: ${response.status}`);
    return response.json();
  }

  // Example: last 50 failed calls
  const result = await listCalls({ status: "failed", limit: 50 });
  console.log(`Total failed: ${result.meta.total}`);
  ```

  ```javascript JavaScript — Extract transcript theme={null}
  async function getTranscript(callId) {
    const response = await fetch(
      `https://api.truedy.ai/api/public/v1/calls/${callId}`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } }
    );

    const call = await response.json();

    if (!call.transcript) {
      console.log("Transcript not yet available");
      return null;
    }

    // Format as readable text
    return call.transcript
      .map((turn) => {
        const speaker = turn.speaker === "agent" ? "Agent" : "Contact";
        return `[${turn.timestamp.toFixed(1)}s] ${speaker}: ${turn.text}`;
      })
      .join("\n");
  }

  const text = await getTranscript("call_01hx3k9p2qw7v8n4m6j5r0y1tz");
  console.log(text);
  ```
</CodeGroup>

***

## Working with Transcripts

The `transcript` field on a completed call is an ordered array of conversation turns. Each turn has three properties:

| Property    | Type   | Description                                             |
| ----------- | ------ | ------------------------------------------------------- |
| `speaker`   | string | Either `"agent"` or `"contact"`                         |
| `text`      | string | The words spoken in this turn                           |
| `timestamp` | number | Seconds from the start of the call when this turn began |

### Example Transcript

```json theme={null}
[
  {
    "speaker": "agent",
    "text": "Hi Sarah, this is Aria calling from Bright Dental. How are you today?",
    "timestamp": 1.2
  },
  {
    "speaker": "contact",
    "text": "I'm good, thanks. What's this about?",
    "timestamp": 5.8
  },
  {
    "speaker": "agent",
    "text": "I'm calling to confirm your appointment on April 10th at 2 PM. Does that still work for you?",
    "timestamp": 9.1
  },
  {
    "speaker": "contact",
    "text": "Yes, that works perfectly.",
    "timestamp": 15.3
  }
]
```

<Note>
  Transcripts are generated asynchronously after the call ends. If you retrieve a call immediately after it completes, the `transcript` field may be `null` for a few seconds while processing finishes. The `call.analyzed` webhook event fires once the transcript is ready.
</Note>

### Extracting a Plain-Text Transcript

If you need to store or display the transcript as plain text, join the turns with speaker labels:

```python Python theme={null}
def format_transcript(transcript):
    lines = []
    for turn in transcript:
        speaker = "Agent" if turn["speaker"] == "agent" else "Contact"
        lines.append(f"{speaker}: {turn['text']}")
    return "\n".join(lines)
```

***

## Accessing Recordings

The `recording_url` field on an ended call contains a **signed URL** pointing to the MP3 audio recording of the conversation.

<Warning>
  Recording URLs are time-limited. They expire after **24 hours**. If you need to retain recordings long-term, download the audio file to your own storage (e.g. S3, GCS) promptly after the call ends or when you receive the `call.analyzed` webhook.
</Warning>

### Downloading a Recording

<CodeGroup>
  ```bash curl theme={null}
  # Download the recording to a local file
  curl -L "https://recordings.truedy.ai/..." -o call_recording.mp3
  ```

  ```python Python theme={null}
  import requests

  def download_recording(call_id, output_path):
      # First fetch the call to get the recording URL
      call_response = requests.get(
          f"https://api.truedy.ai/api/public/v1/calls/{call_id}",
          headers={"Authorization": "Bearer YOUR_API_KEY"},
      )
      call = call_response.json()

      if not call.get("recording_url"):
          print("No recording available yet")
          return

      # Download the audio file
      audio_response = requests.get(call["recording_url"])
      audio_response.raise_for_status()

      with open(output_path, "wb") as f:
          f.write(audio_response.content)

      print(f"Recording saved to {output_path}")

  download_recording("call_01hx3k9p2qw7v8n4m6j5r0y1tz", "recording.mp3")
  ```

  ```javascript JavaScript theme={null}
  async function downloadRecording(callId) {
    // Fetch call to get recording URL
    const callResponse = await fetch(
      `https://api.truedy.ai/api/public/v1/calls/${callId}`,
      { headers: { Authorization: "Bearer YOUR_API_KEY" } }
    );
    const call = await callResponse.json();

    if (!call.recording_url) {
      console.log("No recording available");
      return null;
    }

    // Fetch the audio data
    const audioResponse = await fetch(call.recording_url);
    const audioBuffer = await audioResponse.arrayBuffer();

    // In Node.js, save to disk:
    const fs = await import("fs/promises");
    await fs.writeFile("recording.mp3", Buffer.from(audioBuffer));
    console.log("Recording saved to recording.mp3");
  }
  ```
</CodeGroup>

***

## Call Analytics Fields

In addition to the transcript and recording, each ended call includes analytics metadata:

| Field              | Type    | Description                                                                                                                         |
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `duration_seconds` | integer | Total call duration from answer to hang-up                                                                                          |
| `cost_cents`       | integer | Cost of the call in USD cents. Useful for billing reconciliation                                                                    |
| `outcome`          | string  | Post-call analysis label applied by Truedy AI (e.g. `interested`, `not-interested`, `voicemail`, `no-answer`, `callback-requested`) |

<Tip>
  The `outcome` field is powered by Truedy's post-call analysis and reflects the result of the conversation as determined by the AI. You can customise the outcome labels and logic in your agent's configuration under **Post-Call Analysis**.
</Tip>

***

## Pagination

The calls list endpoint returns up to 100 records per request. Use `limit` and `offset` to paginate through large result sets.

```python Python — Fetch all calls with pagination theme={null}
import requests

def fetch_all_calls(**filters):
    all_calls = []
    offset = 0
    limit = 100

    while True:
        response = requests.get(
            "https://api.truedy.ai/api/public/v1/calls",
            headers={"Authorization": "Bearer YOUR_API_KEY"},
            params={"limit": limit, "offset": offset, **filters},
        )
        result = response.json()
        all_calls.extend(result["data"])

        if offset + limit >= result["meta"]["total"]:
            break

        offset += limit

    return all_calls

# Fetch every ended call from last week
calls = fetch_all_calls(
    status="ended",
    created_after="2024-04-01T00:00:00Z",
    created_before="2024-04-08T00:00:00Z",
)
print(f"Fetched {len(calls)} calls")
```

***

## Filtering by Date

Use the `created_after` and `created_before` query parameters to narrow results to a specific time window. Both accept ISO 8601 datetime strings.

```
# Calls from a specific day (UTC)
GET /calls?created_after=2024-04-05T00:00:00Z&created_before=2024-04-06T00:00:00Z

# Calls from the last 7 days (compute dynamically)
GET /calls?created_after=2024-03-29T00:00:00Z
```

<Note>
  All timestamps in the Truedy API are in UTC. Make sure to convert local times to UTC before constructing date filter parameters.
</Note>

***

## Exporting Call Data

Truedy does not currently provide a bulk CSV export endpoint. There are two recommended patterns for building your own export pipeline:

### Pattern 1: Paginated API Pull

Write a script that paginates through `GET /calls` for your desired date range and writes results to a file or database. The Python pagination example above demonstrates this approach. Run it on a schedule (e.g. nightly) to keep an external data store up to date.

### Pattern 2: Webhook + Store

For real-time exports, configure a `call.analyzed` webhook endpoint on your server. Each time a call completes, Truedy pushes the full call object — including transcript, outcome, and analytics — to your endpoint. Your server writes the record to your database, data warehouse, or CRM immediately.

This pattern requires a publicly accessible server but avoids the need to poll the API.

<Tip>
  For teams using CRMs or analytics tools, the webhook + store pattern is the most robust approach. It captures every call in real time and ensures you never miss a record due to pagination or timing issues.
</Tip>

See [Webhooks Overview](/guides/webhooks-overview) for webhook configuration details.

***

## Data Retention

Truedy retains call records (metadata, transcripts) for **12 months** from the call date. Recording audio files are available via signed URL for **24 hours** after the call ends — after that, the link expires and the audio is no longer accessible through the API.

If you need to retain recordings beyond 24 hours or call records beyond 12 months, implement your own archival process using the webhook + store pattern described above.

***

## Next Steps

<Columns>
  <Card title="Calls Overview" icon="phone" href="/guides/calls-overview">
    Call statuses, directions, and lifecycle
  </Card>

  <Card title="Making Outbound Calls" icon="phone-arrow-up-right" href="/guides/making-outbound-calls">
    Place individual outbound calls via the API
  </Card>

  <Card title="Webhooks Overview" icon="bolt" href="/guides/webhooks-overview">
    Receive real-time call events on your server
  </Card>

  <Card title="Post-Call Analysis" icon="chart-bar" href="/guides/post-call-analysis">
    Understand outcome labels and call summaries
  </Card>
</Columns>
