# Working with analysis results

**Availability: Available**

RelationsAI analyses each completed conversation. This guide explains what the analysis contains, when it is available and how to map it into your own systems.

## Before you begin

- A key with `analysis:read`.
- An agent configured in the console with the fields you want extracted and, optionally, a goal. Analysis reports only what the agent was configured to look for.
- A settled call. The requests below use the documented example id; a test call to `+12025550100` has a complete fixture analysis.

## What analysis contains

| Part | Field | Notes |
|---|---|---|
| Summary | `summary.text` | A short written summary of the conversation |
| Extracted fields | `extracted_fields[]` | One entry per field configured on the agent: `name`, `type` (for example `string`, `number`, `boolean`; open enum), `state` and `value` |
| Goal | `goal_met` | `true` or `false` when the agent is configured to report a goal, otherwise `null` |

Each part has its own state, because parts are produced separately: `summary.state` and each field's `state` are `pending`, `ready`, `absent` or `failed`. A field that is `absent` was not mentioned in the call: its `value` is `null`. The overall `state` is `pending` while any part is still being produced, `ready` once every part has finished (a part can still be `failed`), and `absent` when the call produced nothing to analyse.

Sentiment is not part of analysis today; `capabilities.sentiment_analysis` on [`GET /workspace`](/api/workspace#retrieve-the-workspace) is `false`.

## Reading analysis

Fetch [`GET /calls/{call_id}/analysis`](/api/analysis#retrieve-a-call-analysis) after the call is settled (on `call.settled`, or once `settled` is `true`).

```bash
curl -X GET https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/analysis \
     -H "Authorization: Bearer <token>"
```
```js
const response = await fetch("https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/analysis", {
  method: "GET",
  headers: {
    "Authorization": "Bearer <token>",
  },
});
const analysis = await response.json();
```
```python

response = requests.get(
    "https://voice-api.relationsai.com/v1/calls/call_6Vb3Nm8Qw2Er5Ty9Ui4Op7/analysis",
    headers={
        "Authorization": "Bearer <token>",
    },
    timeout=30,
)
analysis = response.json()
```

Complete runnable example: [cURL script](/examples/guides/working-with-analysis-results/get-analysis.sh) · [JavaScript](/examples/guides/working-with-analysis-results/get-analysis.mjs) · [Python](/examples/guides/working-with-analysis-results/get-analysis.py)

A ready analysis (`200`):

```json
{
  "object": "call_analysis",
  "livemode": true,
  "call_id": "call_2Hd8Ks4Lq7Mw3Nx9Pz5Rc1",
  "state": "ready",
  "simulated": false,
  "summary": {
    "state": "ready",
    "text": "The customer confirmed the renewal and asked for a callback on Friday afternoon."
  },
  "extracted_fields": [
    { "name": "wants_renewal", "type": "boolean", "state": "ready", "value": true },
    { "name": "callback_day", "type": "string", "state": "ready", "value": "Friday" }
  ],
  "goal_met": true
}
```

Read before the call settles and you get `200` with `state: "pending"`, an empty `extracted_fields` and `summary.text: null`. That is not an error: read again after `call.settled`.

## Mapping to your systems

Put your own record id in the call's `metadata` when you [place it](/guides/making-an-outbound-call) (for example `crm_record_id`). The `call.settled` event carries the call with its `metadata`, so your handler knows which record to update without a lookup table:

1. On `call.settled`, take `data.object.id` and `data.object.metadata.crm_record_id`.
2. Fetch the analysis.
3. Write `summary.text` to a note or activity on the record, map each `ready` field to a field on your side by `name`, and record `goal_met`.
4. Store the call id with what you wrote, so a duplicate event is a no-op.

Practical rules:

- Map by field `name`, and check `type` before writing a value into a typed column. A field you do not recognise (someone added it to the agent) should be logged and skipped, not crash the handler.
- Write only `ready` values. `absent` means the topic did not come up: do not overwrite an existing value in your system with `null`.
- Keep the call id next to the values you write, so anyone can trace a value back to the conversation and its transcript.

The [CRM integration guide](/guides/integrating-with-crm-systems) shows the full flow, and the [webhook receiver](/guides/receiving-webhooks) fetches the analysis on `call.settled`.

## Limits

Analysis is produced automatically from the conversation and can be wrong: a field can be misheard, a summary can miss a nuance, a goal can be judged met when it was not. Treat it as a strong hint:

- Keep a person in the loop for decisions that matter to the person called (credit, health, legal, employment) and let them check the transcript.
- Prefer fields with a small set of values (yes/no, a day of the week) over free text for automation.
- Test-mode analysis is a fixture marked `simulated: true`; it does not reflect your agent's configuration.

## What can go wrong

- **`state: "absent"`**: The call did not connect or nobody spoke. Fix: Nothing to read; check the call's `outcome`.
- **`state: "pending"`**: The call has not settled. Fix: Wait for `call.settled`.
- **A field is always `absent`**: The agent never asks about it, or it is not configured. Fix: Adjust the agent in the console and publish.
- **[`insufficient_scope`](/errors/insufficient_scope) (403)**: The key lacks `analysis:read`. Fix: Ask an admin for a key with it.
- **[`resource_not_found`](/errors/resource_not_found) (404)**: Wrong mode or workspace for this call id. Fix: Use the key that created the call.

## Next steps

[Running campaigns](/guides/running-campaigns).
