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

# Conversations

> Schedule and manage AI-run calls with the Conversations API

<Info>
  This resource is also `/conversations`. `/interviews` is a permanent alias that keeps working unchanged; the newer name is the one to reach for in new code. See [Resource names](/api-reference/introduction#resource-names).
</Info>

## Overview

A conversation is one AI-run call to one contact, by phone or on the web. Each is run by an agent and leaves behind a transcript, a recording, and either an analysis or the analytics the agent was configured to extract.

## Resource Structure

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "contactId": "contact-uuid",
  "candidateId": "contact-uuid",
  "agentId": "agent-uuid",
  "runId": null,
  "status": "COMPLETED",
  "scheduledAt": "2024-01-20T14:00:00Z",
  "durationMinutes": 23,
  "finishedDate": "2024-01-20T14:25:30Z",
  "metadata": {
    "externalId": "CONV-789"
  },
  "callAttempts": [
    {
      "id": "attempt-uuid",
      "direction": "OUTBOUND",
      "status": "COMPLETED",
      "scheduledAt": "2024-01-20T14:00:00Z",
      "calledAt": "2024-01-20T14:00:15Z",
      "duration": 1395,
      "recordingUrl": "https://storage.example.com/recordings/conversation.mp3"
    }
  ],
  "analysis": {
    "id": "analysis-uuid",
    "general": {
      "overallRating": 85,
      "companyFitRating": "HIGH",
      "education": "MASTER_LEVEL",
      "experience": "THREE_TO_5_YEARS",
      "strongPoints": ["Strong technical skills", "Clear communication"],
      "weakPoints": ["Limited leadership experience"],
      "evaluation": ["Strong hire for senior individual contributor role"],
      "status": 3,
      "createdAt": "2024-01-20T14:30:00Z",
      "updatedAt": "2024-01-20T14:30:00Z"
    },
    "specific": null
  },
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-20T14:25:30Z"
}
```

<Note>
  `runId` is `null` on a conversation created on its own, and carries the run's id on one a
  [run](/guides/resources/runs) produced — a run mints one conversation per contact. The
  `conversation.*` and `analysis.*` [webhook payloads](/guides/webhooks#payload-structure) carry
  it too, so a batch can be attributed without reading the conversation back.
</Note>

<Note>
  The `analysis` above is the recruiting pipeline's output. A conversation run by a [custom
  agent](/guides/resources/agents#custom-agents) instead carries an
  [`analytics`](#custom-agent-analytics) object beside it — usually **without** an `analysis`,
  since none of the hiring jobs apply to it.
</Note>

## Required Scopes

| Operation              | Required Scope         | Additional                     |
| ---------------------- | ---------------------- | ------------------------------ |
| List conversations     | `read:conversations`   | `read:contacts`, `read:agents` |
| Get conversation by ID | `read:conversations`   |                                |
| Create conversation    | `write:conversations`  | `read:contacts`, `read:agents` |
| Delete conversation    | `delete:conversations` |                                |

The legacy aliases (`read:interviews`, `write:interviews`, `delete:interviews`) work identically and stay valid on keys that already carry them — see
[Scopes and Permissions](/guides/scopes-and-permissions).

## Creating Conversations

### With an Existing Contact and Agent

```javascript theme={null}
const conversation = await fetch("https://api.instaview.sk/conversations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.INSTAVIEW_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    contactId: "contact-uuid",
    jobId: "job-uuid", // Optional: which job (required when the contact holds several). Or use `job` to define one inline.
    agentId: "agent-uuid",
    scheduleTime: "2024-01-20T14:00:00Z", // Optional: ISO 8601, max 30 days out. Omit to start as soon as possible.
  }),
});
```

<Info>
  **Job selection**: with an existing `contactId` you may name a `jobId` to record which job
  the conversation is for. It is **required** when the contact is associated with several jobs.
  The job must belong to your API key's company, but it need not be one the contact already
  holds: if they are not assigned to it, creating the conversation assigns them.
</Info>

### With an Inline Contact

Create a conversation with a contact that does not exist yet. A job is optional:

**With Job Association:**

```javascript theme={null}
const conversation = await createConversation({
  contact: {
    jobId: "job-uuid",
    firstName: "Jane",
    lastName: "Doe",
    email: "jane@example.com",
    phoneNumber: "+1234567890",
    gdprExpiryDate: "2026-11-16",
  },
  agentId: "agent-uuid",
  scheduleTime: "2024-01-20T14:00:00Z",
});
```

**Without Job Association:**

```javascript theme={null}
const conversation = await createConversation({
  contact: {
    firstName: "John",
    lastName: "Smith",
    email: "john@example.com",
    phoneNumber: "+1234567890",
    gdprExpiryDate: "2026-11-16",
    // jobId is optional - the contact is created without a job assignment
  },
  agentId: "agent-uuid",
  scheduleTime: "2024-01-20T14:00:00Z",
});
```

<Info>
  **Inline contact fields**: `gdprExpiryDate` is required, and must be a valid ISO 8601 date —
  it is when this person's data stops being yours to keep. `jobId` is optional; omit it to
  create the contact with no job assignment and attach jobs later through the [update contact
  endpoint](/api-reference/contacts/update-contact).
</Info>

### With an Inline Job (Existing Contact)

Create a conversation for a contact you already hold while defining the job inline — useful when you know the job but do not want to create it as a separate request first.

```javascript theme={null}
const conversation = await createConversation({
  contactId: "contact-uuid",
  job: {
    jobTitle: "Senior Backend Engineer",
    jobDescription: "We are looking for a senior engineer...",
    requiredSkills: ["TypeScript", "NestJS", "PostgreSQL"],
    niceToHaveSkills: ["Redis", "AWS"],
    languages: ["EN"],
    experience: "SENIOR",
    contractType: "FULL_TIME",
  },
  agentId: "agent-uuid",
  scheduleTime: "2024-01-20T14:00:00Z",
});
```

<Info>
  **Inline Job Fields**: - `jobTitle` is required and must be between 5–200
  characters. - Arrays like `requiredSkills` and `niceToHaveSkills` accept up to
  10 skills each; each skill is a non-empty string up to 128 characters.
  Combined total cannot exceed 10 skills. - Enum fields (such as `languages`,
  `education`, `experience`, `contractType`) use the same values as the Jobs
  API. - Nested objects `location` and `salary` reuse the core Job DTOs, so data
  stored is identical to jobs created via the Jobs API. The inline job is
  persisted as a regular **Job** during creation, and the conversation is
  linked to it.
</Info>

<Info>
  **XOR with `jobId`**: - Use `jobId` when you already have a job created. - Use
  `job` when you want to define the job inline. - You must not send both `jobId`
  and `job` in the same request (XOR behavior).
</Info>

### With an Inline Contact and an Inline Job

Define both inline in a single request, when you already hold everything you need:

```javascript theme={null}
const conversation = await createConversation({
  contact: {
    firstName: "Jane",
    lastName: "Doe",
    email: "jane@example.com",
    phoneNumber: "+1234567890",
    gdprExpiryDate: "2026-11-16",
  },
  job: {
    jobTitle: "Senior Backend Engineer",
    jobDescription: "We are looking for a senior engineer...",
    requiredSkills: ["TypeScript", "NestJS", "PostgreSQL"],
    niceToHaveSkills: ["Redis", "AWS"],
    languages: ["EN"],
    experience: "SENIOR",
    contractType: "FULL_TIME",
  },
  agentId: "agent-uuid",
  scheduleTime: "2024-01-20T14:00:00Z",
});
```

<Info>
  **Complete inline workflow**: combining `contact` with `job` - creates a new **Job** for your
  company - creates a new **Contact**, automatically linked to that job - associates the
  **Conversation** with both - and leaves all three as permanent resources. Nothing has to be
  created in an earlier request.
</Info>

<Info>
  **Ways to name the job**: 1. **Inline**: `job` at the top level defines and creates it in one
  request 2. **By reference**: `jobId` at the top level, for a job that exists 3. **Compact**:
  `jobId` inside `contact`, when creating the contact in the same request. **Priority**: if
  several are sent, `job` > top-level `jobId` > `contact.jobId`. **Do not** combine
  `contact.jobId` with a top-level `jobId` or `job` — which job you meant would be a guess.
</Info>

### Scheduling

<Warning>
  **Scheduling Constraint**: The `scheduleTime` must be a valid ISO 8601
  date-time string and cannot exceed **30 days in the future**. Attempts to
  schedule beyond this limit will result in a `400 Bad Request` error.
</Warning>

`scheduleTime` is optional. Omit it to place the call as soon as the agent's calling hours allow, or send a future timestamp to pin it.

```javascript theme={null}
// As soon as possible (omit scheduleTime)
{
  contactId: 'contact-uuid',
  agentId: 'agent-uuid'
}

// Scheduled for later
{
  contactId: 'contact-uuid',
  agentId: 'agent-uuid',
  scheduleTime: '2024-01-20T14:00:00Z' // Optional: ISO 8601 format, max 30 days in future
}
```

## Deleting Conversations

Deleting a conversation removes it permanently, along with its transcript, recording and analysis.

```javascript theme={null}
async function deleteConversation(conversationId) {
  const response = await fetch(
    `https://api.instaview.sk/conversations/${conversationId}`,
    {
      method: "DELETE",
      headers: {
        Authorization: `Bearer ${process.env.INSTAVIEW_API_KEY}`,
      },
    }
  );

  const data = await response.json();
  return data; // { id, deleted: true }
}
```

<Info>
  The conversation row itself is deleted outright, and its transcripts, recordings and analyses
  go with it — a deleted conversation is gone from list and get, and no endpoint returns it
  again. See [Delete Conversation](/api-reference/conversations/delete-conversation) for what
  happens to a call that is in progress at the time.
</Info>

## Conversation Status

`status` moves through the non-terminal values and settles on one of four terminal values. A terminal status holds until the conversation is deliberately reopened — see [Reopening a terminal conversation](#reopening-a-terminal-conversation) below.

| Status        | Terminal | Meaning                                                                                                                                                             |
| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SCHEDULED`   | No       | An attempt is queued and waiting. `scheduledAt` holds its time. A retried conversation reads this between attempts, so it does not imply no call has been made yet. |
| `IN_PROGRESS` | No       | A call is live right now. It returns to `SCHEDULED` once the attempt ends and the next one is queued.                                                               |
| `COMPLETED`   | Yes      | The call happened. Transcript, recording and results follow.                                                                                                        |
| `UNREACHABLE` | Yes      | Every permitted attempt was made and the contact never picked up — no answer, busy, or voicemail.                                                                   |
| `FAILED`      | Yes      | A technical failure prevented the call being placed or held.                                                                                                        |
| `CANCELLED`   | Yes      | Called off deliberately, from the InstaView dashboard or by InstaView staff.                                                                                        |
| `UNDEFINED`   | No       | Not yet initialised. Transient; you should not observe it in practice.                                                                                              |

<Note>
  **`UNREACHABLE` is new as of August 2026.** It splits the "we could not reach them"
  cohort out of `FAILED` and `CANCELLED`, which previously carried it
  inconsistently depending on where the retry budget ran out. If you filter or report on
  either of those, add `UNREACHABLE` — see the [August 2026 changelog](/changelog/2026-08).
</Note>

### Reopening a terminal conversation

Rescheduling a conversation that has already settled returns it to `SCHEDULED` and clears `finishedDate`. This applies to every terminal status — `FAILED`, `UNREACHABLE`, `CANCELLED` and `COMPLETED` alike. It keeps the same id and runs to an outcome as normal, so a terminal status is better read as *terminal until somebody asks for another call* than as final.

Only a deliberate action reopens one — a reschedule, an automatic retry, or an explicitly invoked attempt. Nothing reopens on its own. A reopened `COMPLETED` conversation keeps every transcript, recording and analysis it already has: those belong to the attempts that produced them, not to the conversation's current status, so calling again adds to the record rather than replacing it.

<Warning>
  **If you stop polling when a conversation goes terminal**, subscribe to
  [`conversation.rescheduled`](/guides/webhooks#conversation-rescheduled-payload) and resume
  when it fires. It is emitted on every reopen, so a poller that ignores it will never see
  the outcome of the new call. Until 28 August 2026 a terminal status was documented as
  never changing again — see the [August 2026 changelog](/changelog/2026-08).
</Warning>

## Retrieving Results

### Get a Conversation with its Analysis

```javascript theme={null}
async function getConversationResults(conversationId) {
  const response = await fetch(
    `https://api.instaview.sk/conversations/${conversationId}`,
    { headers: { Authorization: `Bearer ${apiKey}` } }
  );

  const data = await response.json();

  return {
    status: data.status,
    durationMinutes: data.durationMinutes,
    finishedDate: data.finishedDate,
    overallRating: data.analysis?.general?.overallRating,
    strongPoints: data.analysis?.general?.strongPoints,
    weakPoints: data.analysis?.general?.weakPoints,
    evaluation: data.analysis?.general?.evaluation,
    specific: data.analysis?.specific,
  };
}
```

### List a Contact's Conversations

```javascript theme={null}
async function getContactConversations(contactId) {
  const response = await fetch(
    `https://api.instaview.sk/conversations?contactId=${contactId}`,
    { headers: { Authorization: `Bearer ${apiKey}` } }
  );

  return await response.json();
}
```

## Custom Agent Analytics

A conversation run by a [custom agent](/guides/resources/agents#custom-agents) carries a second, separate object: **`analytics`**, holding whatever that agent's `analyticsConfig` asked for.

It is a **sibling** of `analysis`, not a variant of it. `analysis` is the recruiting pipeline's output and is typically **absent** on a custom call — there is no `analysisPdfBase64` either, since the PDF report is a recruiting artefact. `analytics` is the agent's own design, and it is the only one of the two that appears outside hiring.

```json theme={null}
{
  "analytics": {
    "matchScore": 72,
    "fields": [
      { "key": "budget_confirmed", "label": "Budget confirmed", "type": "bool",
        "value": true, "confidence": 0.9, "evidence": "Signed off on 15k this quarter" },
      { "key": "timeline", "label": "Timeline", "type": "string", "value": null }
    ],
    "scoring": {
      "total": 72,
      "items": [
        { "key": "budget_confirmed", "kind": "field", "label": "Budget confirmed",
          "score": 90, "weightPercent": 45.4, "importance": "REQUIRED" }
      ]
    },
    "outcomes": [{ "key": "meeting_booked", "label": "Meeting booked", "met": true }],
    "tags": [{ "key": "needs_follow_up", "label": "Needs follow-up", "applied": false }],
    "qa": [
      { "id": "3f1a…-question-uuid", "question": "What are you using today?",
        "answered": true, "answer": "Spreadsheets, rebuilt by hand every week",
        "evidence": "It's the worst two hours of my Monday" }
    ],
    "evaluation": {
      "strengths": ["Clear pain point, already shopping for a replacement"],
      "concerns": ["Finance sign-off not secured"],
      "objections": ["Worried about migrating two years of historical data"],
      "assessment": ["Strong fit; the only real risk is procurement timing"]
    },
    "sentiment": { "overall": "positive", "score": 0.8, "trajectory": "improving" },
    "summary": "Qualified lead; demo booked for Thursday.",
    "updatedAt": "2026-08-04T14:30:00Z"
  }
}
```

Reading it:

* The `key` on each field, outcome and tag is the one configured on the agent, so you can switch on it directly.
* **`value: null` means the call did not surface it** — distinct from a `false` or empty value that *was* surfaced. A contact who declines to answer gives you `null`; one who says "no" gives you `false`. `confidence` and `evidence` are omitted entirely when the value is `null`.
* **`weightPercent` is each item's share of the match score**, and the shares sum to exactly 100. The weights you configured are relative, not percentages, so a raw `50` tells a consumer nothing on its own.
* In `scoring.items`, an entry with `kind: "question"` carries the flow question's **id** rather than a configured key — a question has none, which is why its scoring is set inline on the question. `kind: "field"` and `"criterion"` carry the configured keys.
* `matchScore` is always present, and `null` when scoring has not run yet.
* In `qa`, **`answered: false` means the call never reached the question** — not that it was answered with nothing. `answer` is an empty string in both cases, so switch on `answered`, not on the string.
* `evaluation` is four independent arrays of lines (`strengths`, `concerns`, `objections`, `assessment`). Any one of them can be empty while the others are populated, so check each rather than the object.
* Each slice comes from its own after-call job, and each runs only when the agent asked for it — so an absent `outcomes` or `sentiment` means "not configured, or not run yet".

<Info>
  The same object is pushed to your `analysis.completed`
  [webhook](/guides/webhooks#analysis-events-payload), so you do not have to poll for it. It
  also appears on every row of `GET /conversations`.
</Info>

## Analysis Structure

<Note>
  This section describes `analysis` — the **recruiting** pipeline's output, produced for
  agents with a hiring `focus`. For a custom agent, see [Custom Agent
  Analytics](#custom-agent-analytics) above.
</Note>

The analysis object is the hiring assessment. It contains a `general` object holding the overall verdict and an optional `specific` object carrying whatever the agent's focus produces.

### General Analysis Fields

| Field                     | Type           | Description                                                             |
| ------------------------- | -------------- | ----------------------------------------------------------------------- |
| `overallRating`           | number \| null | Overall rating (0-100). `null` when scoring did not complete            |
| `scoringIncompleteReason` | string         | Why no rating was produced: `model_unavailable`, `model_error`          |
| `companyFitRating`        | string         | Company fit: `UNDEFINED`, `LOW`, `MEDIUM`, `HIGH`                       |
| `education`               | string         | Education level (see enum values below)                                 |
| `experience`              | string         | Experience level (see enum values below)                                |
| `strongPoints`            | string\[]      | Strong points and advantages                                            |
| `weakPoints`              | string\[]      | Areas for improvement                                                   |
| `evaluation`              | string\[]      | Overall evaluation summary and recommendations                          |
| `status`                  | number         | Analysis status: `1`=PENDING, `2`=PROCESSING, `3`=COMPLETED, `4`=FAILED |
| `createdAt`               | string         | ISO 8601 timestamp                                                      |
| `updatedAt`               | string         | ISO 8601 timestamp                                                      |

#### When there is no rating

`overallRating` comes back `null` when the model that scores the conversation could not be reached.
It is not zero and not a low score: no rating was produced. `scoringIncompleteReason` says why, and it
is the field to branch on rather than treating a missing rating as a bad one. A conversation that was
never analysed also has a `null` rating, and no `scoringIncompleteReason`.

| Reason              | What happened                                                     | What we do next                                                              |
| ------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `model_unavailable` | The scoring model could not be reached and the retries were spent | The analysis is queued to run again, spaced over roughly seven minutes       |
| `model_error`       | It answered unusably, or the failure was permanent                | Nothing automatic; the analysis stays as it is with the reason on the record |

The rest of the analysis is unaffected — `strongPoints`, `weakPoints`, `evaluation` and the
`specific` data are produced by different calls and are present as usual. On a `model_unavailable`
run that recovers, the rating appears and the reason disappears; the
[`analysis.completed` webhook](/guides/webhooks#analysis-events-payload) fires again with the
filled-in analysis.

```javascript theme={null}
// Sort a shortlist without treating "not scored" as "scored zero".
const rated = conversations.filter((c) => c.analysis?.general?.overallRating != null);
const unscored = conversations.filter((c) => c.analysis?.general?.scoringIncompleteReason);
```

**Education Level Values**: `UNDEFINED`, `PRIMARY_EDUCATION`, `SECONDARY_SCHOOL_STUDENT`, `SECONDARY_WITHOUT_DIPLOMA`, `SECONDARY_WITH_DIPLOMA`, `POST_SECONDARY_VOCATIONAL`, `UNIVERSITY_STUDENT`, `BACHELOR_LEVEL`, `MASTER_LEVEL`, `POSTGRADUATE`

**Experience Level Values**: `UNDEFINED`, `NO_EXPERIENCE`, `LESS_THAN_1_YEAR`, `ONE_TO_3_YEARS`, `THREE_TO_5_YEARS`, `FIVE_TO_10_YEARS`, `TEN_TO_15_YEARS`, `MORE_THAN_15_YEARS`

```javascript theme={null}
{
  analysis: {
    id: "analysis-uuid",
    general: {
      overallRating: 85,
      companyFitRating: "HIGH",
      education: "MASTER_LEVEL",
      experience: "THREE_TO_5_YEARS",
      strongPoints: [
        "Strong technical skills in React and Node.js",
        "Excellent problem-solving abilities"
      ],
      weakPoints: [
        "Limited experience with cloud infrastructure"
      ],
      evaluation: [
        "Strong hire for senior IC role",
        "Consider for team lead in 6-12 months"
      ],
      status: 3,
      createdAt: "2024-01-20T14:30:00Z",
      updatedAt: "2024-01-20T14:30:00Z"
    },
    specific: null // or focus-specific data
  }
}
```

## Conversation Types & Analysis

What lands in `specific` depends on the agent's focus. There are four hiring focuses; a [custom agent](#custom-agent-analytics) has none of them and produces `analytics` instead.

<Tabs>
  <Tab title="SCREENING">
    **Standard hiring screen**

    Evaluates the contact against the job's requirements. The analysis uses only the `general` fields — there is no `specific` data.

    ```json theme={null}
    {
      "analysis": {
        "id": "analysis-uuid",
        "general": {
          "overallRating": 85,
          "companyFitRating": "HIGH",
          "education": "MASTER_LEVEL",
          "experience": "THREE_TO_5_YEARS",
          "strongPoints": ["Strong technical background", "Good communication"],
          "weakPoints": ["Limited management experience"],
          "evaluation": ["Recommended for next round"],
          "status": 3,
          "createdAt": "2024-01-20T14:30:00Z",
          "updatedAt": "2024-01-20T14:30:00Z"
        },
        "specific": null
      }
    }
    ```
  </Tab>

  <Tab title="OUTREACH">
    **Recruitment outreach calls**

    A first call to gauge interest. `specific` carries:

    | Field               | Type   | Description                        |
    | ------------------- | ------ | ---------------------------------- |
    | `interestType`      | string | How interested the contact sounded |
    | `specificFeedback`  | string | What they said, in detail          |
    | `preferredNextStep` | string | The next step they asked for       |
    | `rescheduleTime`    | number | A callback time they named, if any |

    ```json theme={null}
    {
      "analysis": {
        "id": "analysis-uuid",
        "general": {
          "overallRating": 75,
          "companyFitRating": "MEDIUM",
          "strongPoints": ["Interested in the role", "Available immediately"],
          "weakPoints": ["Salary expectations may be high"],
          "evaluation": ["Schedule follow-up interview"],
          "status": 3,
          "createdAt": "2024-01-20T14:30:00Z",
          "updatedAt": "2024-01-20T14:30:00Z"
        },
        "specific": {
          "interestType": "INTERESTED",
          "specificFeedback": "Candidate expressed strong interest in the backend role",
          "preferredNextStep": "Technical interview next week"
        }
      }
    }
    ```
  </Tab>

  <Tab title="GENERIC">
    **Job-optional conversations**

    For calls that are not about a particular job, so no job assignment is needed. Same analysis shape as OUTREACH:

    | Field               | Type   | Description                        |
    | ------------------- | ------ | ---------------------------------- |
    | `interestType`      | string | How interested the contact sounded |
    | `specificFeedback`  | string | What they said, in detail          |
    | `preferredNextStep` | string | The next step they asked for       |
    | `rescheduleTime`    | number | A callback time they named, if any |

    ```json theme={null}
    {
      "analysis": {
        "id": "analysis-uuid",
        "general": {
          "overallRating": 70,
          "companyFitRating": "MEDIUM",
          "strongPoints": ["Open to opportunities"],
          "weakPoints": ["Not actively looking"],
          "evaluation": ["Add to talent pool for future positions"],
          "status": 3,
          "createdAt": "2024-01-20T14:30:00Z",
          "updatedAt": "2024-01-20T14:30:00Z"
        },
        "specific": {
          "interestType": "CALLBACK_REQUESTED",
          "specificFeedback": "Prefers to be contacted in Q2",
          "preferredNextStep": "Follow up in 3 months"
        }
      }
    }
    ```
  </Tab>

  <Tab title="LANGUAGE_TEST">
    **Language proficiency assessment**

    Rates the contact's language skills against CEFR. `specific` carries the detail:

    | Field                     | Type   | Description                                               |
    | ------------------------- | ------ | --------------------------------------------------------- |
    | `pronunciationScores`     | object | Pronunciation metrics (accuracy, fluency, prosody: 50-95) |
    | `pronunciationAssessment` | object | CEFR pronunciation level (A1-C2) with reasoning           |
    | `grammar`                 | object | Grammar assessment with CEFR level and summary            |
    | `vocabulary`              | object | Vocabulary assessment with CEFR level and summary         |
    | `overallScore`            | object | Combined CEFR level with confidence score                 |
    | `combinedCefrEvaluation`  | object | Final CEFR evaluation with confidence and summary         |

    ```json theme={null}
    {
      "analysis": {
        "id": "analysis-uuid",
        "general": {
          "overallRating": 82,
          "companyFitRating": "HIGH",
          "strongPoints": ["Fluent speaker", "Good vocabulary range"],
          "weakPoints": ["Minor grammatical errors"],
          "evaluation": ["Meets B2 requirement for the position"],
          "status": 3,
          "createdAt": "2024-01-20T14:30:00Z",
          "updatedAt": "2024-01-20T14:30:00Z"
        },
        "specific": {
          "pronunciationScores": {
            "accuracy": 85,
            "fluency": 88,
            "prosody": 82
          },
          "pronunciationAssessment": {
            "estimated_pronunciation_level": "B2",
            "reasoning": "Clear articulation with occasional non-native patterns"
          },
          "grammar": {
            "level": "B2",
            "meets_tested_level": true,
            "summary": "Consistent use of complex structures with minor errors"
          },
          "vocabulary": {
            "level": "C1",
            "meets_tested_level": true,
            "summary": "Rich vocabulary with appropriate professional terminology"
          },
          "overallScore": {
            "level": "B2",
            "confidence": 0.85,
            "summary": "Strong B2 level with elements of C1 vocabulary"
          },
          "combinedCefrEvaluation": {
            "final_level": "B2",
            "confidence": 0.87,
            "summary": "Candidate demonstrates solid B2 proficiency"
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

## Best Practices

<CardGroup cols={2}>
  <Card title="Check Billing" icon="credit-card">
    Verify billing limits before bulk scheduling
  </Card>

  <Card title="Set Realistic Times" icon="clock">
    Leave buffer time, and let the calling window pick the hour
  </Card>

  <Card title="Monitor Status" icon="signal">
    Poll status, or take the webhook instead
  </Card>

  <Card title="Store Analysis" icon="database">
    Save analysis data to your database
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Agents" icon="robot" href="/guides/resources/agents">
    Create and configure the agents that run the calls
  </Card>

  <Card title="Contacts" icon="user" href="/guides/resources/contacts">
    Learn about managing contacts
  </Card>

  <Card title="Delete Conversation" icon="trash" href="/api-reference/conversations/delete-conversation">
    What deletion removes, and what it cannot undo
  </Card>

  <Card title="Billing" icon="dollar-sign" href="/guides/resources/billing">
    Understand call costs and limits
  </Card>
</CardGroup>
