Features

Call Analysis

Extract structured data from call transcripts using AI to automatically capture sentiment, intents, and outcomes.

Call analysis extracts structured data from call transcripts using AI. Use it to automatically capture customer sentiment, intents, outcomes, and any other data you need.

Prefer the dashboard? The video tutorial Review your calls shows how to create an analysis template, apply it to an assistant and see the results on each call.

How It Works

  1. Configure an analysis_plan on your assistant
  2. After each call, the transcript is analyzed
  3. Structured data matching your schema is extracted
  4. Results are included in the end-of-call report webhook

Basic Configuration

Add an analysis plan to your assistant:

curl -X PATCH https://api.hmsovereign.com/api/v1/assistants/ASSISTANT_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "analysis_plan": {
      "structured_data_plan": {
        "enabled": true,
        "schema": {
          "type": "object",
          "properties": {
            "sentiment": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "description": "Customer sentiment score"
            },
            "intent": {
              "type": "string",
              "enum": ["support", "sales", "complaint", "inquiry", "other"]
            },
            "resolved": {
              "type": "boolean",
              "description": "Was the customer issue resolved"
            }
          }
        },
        "messages": [
          {
            "role": "system",
            "content": "Analyze the call transcript and extract structured data according to the schema."
          },
          {
            "role": "user",
            "content": "Schema: {{schema}}\n\nTranscript: {{transcript}}"
          }
        ]
      },
      "min_messages_threshold": 3
    }
  }'

Schema Design

Define the exact data you want to extract using JSON Schema:

{
  "type": "object",
  "properties": {
    "sentiment": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10,
      "description": "Overall customer sentiment (1=very negative, 10=very positive)"
    },
    "primary_intent": {
      "type": "string",
      "enum": ["support", "sales", "complaint", "inquiry", "appointment", "other"],
      "description": "Main reason for the call"
    },
    "topics_discussed": {
      "type": "array",
      "items": { "type": "string" },
      "description": "List of main topics covered in the conversation"
    },
    "action_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "task": { "type": "string" },
          "assigned_to": { "type": "string", "enum": ["assistant", "customer", "other"] }
        }
      }
    },
    "customer_satisfied": {
      "type": "boolean"
    },
    "follow_up_needed": {
      "type": "boolean"
    },
    "summary": {
      "type": "string",
      "description": "Brief summary of the call outcome"
    }
  }
}

Prompt Templates

The messages array defines how the transcript is analyzed. Use placeholders:

PlaceholderDescription
{{schema}}Your JSON Schema (auto-injected)
{{transcript}}Full call transcript
{{ended_reason}}Why the call ended

Example Prompts

Basic analysis:

{
  "messages": [
    {
      "role": "system",
      "content": "You are a call analyst. Extract structured data from the call transcript according to the provided schema. Return valid JSON only."
    },
    {
      "role": "user",
      "content": "Schema: {{schema}}\n\nCall transcript:\n{{transcript}}\n\nThe call ended because: {{ended_reason}}"
    }
  ]
}

Detailed analysis with instructions:

{
  "messages": [
    {
      "role": "system",
      "content": "Analyze customer service calls and extract data according to the schema. Guidelines:\n- Sentiment: 1-3 negative, 4-6 neutral, 7-10 positive\n- Only mark resolved=true if the issue was fully addressed\n- Include all mentioned topics, not just the main one"
    },
    {
      "role": "user",
      "content": "Schema:\n{{schema}}\n\nTranscript:\n{{transcript}}\n\nCall ended: {{ended_reason}}\n\nProvide your analysis as JSON."
    }
  ]
}

Minimum Messages Threshold

Set min_messages_threshold to skip analysis for very short calls:

{
  "min_messages_threshold": 5
}
  • Default: 2 messages
  • Set higher for meaningful analysis
  • Analysis is skipped if threshold not met

Post-Call Transcription

On a speech-to-speech assistant, the transcript comes from the voice model itself, and that transcript is built while the model is listening. It starts at the moment speech is detected, so the first syllables of an answer can be missing, and it leans on the model's own language setting, so a single Dutch word can come out in German. The analysis runs on that text, which means a wrong street name or a wrong colour ends up in your structured data.

The recording does not have that problem. Set postcall_stt_config and the platform transcribes the recording again once the call ends, then uses that transcript for the stored transcript, the summary, the structured analysis and the end-of-call report.

curl -X PATCH https://api.hmsovereign.com/api/v1/assistants/ASSISTANT_ID \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "postcall_stt_config": {
      "provider": "elevenlabs",
      "model": "scribe_v2",
      "language": "nl"
    }
  }'

Leave language out to let the provider detect it. Setting it is worth doing, because keeping the transcript in one language is most of what this step is for.

What it applies to

Speech-to-speech assistants (google_realtime, openai_live, xai_realtime)Available
Pipeline assistants (a text model with stt_config)Rejected with 400

A pipeline assistant already runs the speech-to-text model you picked over the call. That transcript is your choice of model on the audio, so transcribing a second time would add cost and nothing else.

Providers

ProviderModelNotes
elevenlabsscribe_v2Speaker separation and word timestamps
deepgramnova-3, nova-3-generalRuns on Deepgram's EU endpoint

Both use the same key as their live counterpart: your own key when you have one on the account, the platform key otherwise, billed the same way as every other model.

What is replaced, and what is not

Only the caller's side. What the assistant said is the text the model produced and spoke, so it is kept exactly as it was — re-transcribing it would replace something already correct with a guess.

When it does not run

The model's own transcript is used, and the reason is recorded on the call, when:

  • there is no recording, because the assistant runs in GDPR mode or the recording failed
  • the call was transferred, so the recording continues after the assistant has left and the rest is a conversation between your caller and your colleague
  • the transcription does not come back in time, or the two sides cannot be told apart with confidence

It never silently falls back to a worse transcript without saying so, and a failed transcription never delays or drops the end-of-call report.

Timing

The transcription starts the moment the recording stops and runs while the call is being torn down, so in practice the end-of-call report arrives when it always did. A call of a few minutes takes a handful of seconds to transcribe.

Choosing the Analysis Model

The summary and the structured analysis are written by a text model after the call. By default the platform picks a small, fast model that belongs to your assistant's provider. Set postcall_llm_config to choose a different one.

{
  "postcall_llm_config": {
    "provider": "openai",
    "model": "gpt-5.4"
  }
}

When to change it

There are three reasons to set this, and they are all visible in your own results.

The analysis is not good enough. Fields stay empty, a reason comes back vague, or an enum is guessed. A larger model fills more of the schema on a difficult call, especially one with several topics or a caller who changes their mind halfway.

The analysis is too slow. The end-of-call report waits for it, so a slow model delays the whole webhook. A smaller model returns sooner.

You need to know which model reads your call content. The provider and model you set here are the ones that receive the transcript. That is the answer your own processing records need, and you can change it without changing the model that runs the conversation.

Providers and models

Any text provider the platform supports, with its own model list: OpenAI, xAI, Mistral and Google. The full list per provider is in the postcall_llm section of the configuration catalogue, and the API rejects anything outside it at save time rather than at the end of the first call.

You do not have to stay with the provider that runs the conversation. An assistant on a speech-to-speech Gemini model can have its analysis written by an OpenAI model, and the other way round.

Keys and billing

The same rule as every other model: your own key for that provider when you have one, the platform key otherwise, and it is billed either way. Pick a provider you have no key for while no platform key is available, and the summary and analysis are skipped with a reason in the call record — they do not quietly fall back to another model.

The older analysis_model field

llm_config.analysis_model does the same thing and still works, but it is free text: a name the provider does not recognise costs you the analysis, and nothing tells you at save time. postcall_llm_config is checked against the catalogue and wins when both are set. New integrations should use it.

Receiving Analysis Results

Analysis results are included in the end-of-call report webhook:

{
  "message": {
    "type": "end-of-call-report",
    "duration_seconds": 84,
    "summary": "Customer asked about opening hours...",
    "analysis": {
      "sentiment": 8,
      "primary_intent": "inquiry",
      "topics_discussed": ["opening hours", "location", "parking"],
      "customer_satisfied": true,
      "follow_up_needed": false,
      "summary": "Customer received all requested information."
    }
  }
}

Use Cases

Customer Satisfaction Tracking

{
  "schema": {
    "type": "object",
    "properties": {
      "csat_score": { "type": "integer", "minimum": 1, "maximum": 5 },
      "would_recommend": { "type": "boolean" },
      "pain_points": { "type": "array", "items": { "type": "string" } }
    }
  }
}

Sales Lead Qualification

{
  "schema": {
    "type": "object",
    "properties": {
      "lead_quality": { "type": "string", "enum": ["hot", "warm", "cold"] },
      "budget_mentioned": { "type": "boolean" },
      "timeline": { "type": "string" },
      "decision_maker": { "type": "boolean" },
      "products_interested": { "type": "array", "items": { "type": "string" } }
    }
  }
}

Support Ticket Classification

{
  "schema": {
    "type": "object",
    "properties": {
      "category": { "type": "string", "enum": ["billing", "technical", "account", "product", "other"] },
      "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] },
      "resolution_status": { "type": "string", "enum": ["resolved", "escalated", "pending", "unresolved"] },
      "root_cause": { "type": "string" }
    }
  }
}

Best Practices

  1. Keep schemas focused - Extract only what you'll actually use
  2. Use enums - Predefined values make data more consistent
  3. Add descriptions - Help the AI understand what you want
  4. Test prompts - Refine your prompts based on actual results
  5. Set appropriate thresholds - Skip analysis for calls too short to be meaningful

See Analysis Templates and End of Call Report Webhook for complete details.

On this page