Answer detection

What a call carries about who answered it, what happens when only music or tones are heard, and how to change it per agent.

A carrier reports a call answered the moment it starts carrying audio. That is true of a person picking up, and equally true of a ringback replacement tune, a hold-music loop, or a line that answers into silence. So "answered" on its own cannot tell you whether anybody was there.

Every answered call therefore carries two separate facts:

  • its status, which says the transport answered, and
  • its answer_classification, which says what was heard afterwards.

What the classification says

ValueMeaning
humana person was heard
non_human_audiocontinuous music or tones held the line and nothing a person did was ever heard
silentthe line answered and nothing at all was heard
unknowndetection ran and reached no conclusion within its window
""no verdict was reported at all

The last two are not the same thing, and the difference is worth keeping. unknown means detection ran and could not say. The empty string means nothing was reported: the call was placed before this field existed, the agent has collection switched off, or the call never carried media.

reason_detail qualifies the classification and nothing else:

ValueAppears with
caller_tunenon_human_audio
hold_musicnon_human_audio
no_audiosilent
""every other classification

It is not ended_reason. That field says why the call ended; this pair says what was heard when it started. A call can carry both.

Where the fields appear

Both fields are on the call resource returned by GET /v1/calls and GET /v1/calls/{call_id}, on each call in call group results, and in the call.completed webhook body. They are also on the call rows in your workspace.

{
  "call_id": "call_...",
  "status": "completed",
  "answer_classification": "non_human_audio",
  "reason_detail": "hold_music"
}

That is a call that ran its course against hold music. Nothing about how it ended changes today, which is the subject of the next two sections.

What counts as a person

A person is heard when speech starts and then stops. Concretely, either a burst of speech-shaped audio that is followed by a pause, or a recognised word whose audio has come to a stop. The pause is the part that matters: it is what separates somebody saying "hello" from a recording that never breaks.

The reverse verdict is deliberately harder to reach. Skysay only calls audio non-human when it runs continuously for several seconds with no pause in it, its shape lacks the rhythm of speech, and no word is recognised in that window. Anything short of all three is unknown.

That narrowness is on purpose, and it has a stated cost in both directions:

  • A recorded announcement is treated as a person. Announcements and menu prompts have pauses in them, so they read as human or unknown and are left alone. Recognising those is a separate problem and is not what this does.
  • Somebody talking over music that never stops is missed. Their words never land in a pause, so they are not counted, and the call is classified on the audio's shape alone.

Detection runs on the audio itself, in every language. It does not depend on a transcript being available, and it never stores audio: only the verdict, its score and the timings are recorded.

The ending it is paired with

ended_reason has a value, no_human_after_answer, for a call the platform ended because only non-human audio was ever heard after pickup. A call that ends that way carries answer_classification non_human_audio and the detail naming which kind, so the reason says that nobody was there and the classification says what was heard instead.

Nothing ends a call this way today. The value exists in the published vocabulary so that your integration can be ready for it, and the ending itself arrives with a later release. When it does, a person heard at any point before the call ends cancels it, and the agent greets once more, so a late pickup is not hung up on.

A call that ends this way is reported as not connected:

  • it is excluded from a campaign's connect rate, and from talk time;
  • its campaign disposition is no_answer, because no person was reached, so the contact is dialled again under your campaign's existing backoff rather than being treated as a conversation that happened;
  • its termination_cause stays connected, because that field describes the transport and the transport genuinely did answer. The two are separate questions and the fields answer one each;
  • carrier minutes up to the point it ends are real and are billed as usual.

Choose what an agent collects

answer_detection.non_human_audio is set per agent, in the Answer detection card of the agent editor or through PATCH /v1/agents/{agent_id}/voice-stack.

ValueWhat it does
shadowRecords what was heard on every answered call and changes nothing the caller experiences.
offCollects nothing. Calls carry no answer classification at all.

shadow is what an agent with no stored value gets. A third value that acts on the verdict and ends the call arrives with the release that can perform it; until then sending it is refused with 400, because a setting you could select that did nothing would be worse than one that is not offered.

curl -X PATCH https://api.skysay.ai/v1/agents/agt_123/voice-stack \
  -H "Authorization: Bearer $SKYSAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"answer_detection": {"non_human_audio": "off"}}'

Choosing a value is not the same as leaving it alone

This setting deliberately behaves differently from the other per-agent tuning sections. Everywhere else, saving a value equal to the current default is the same as saving nothing. Here it is not:

  • an absent answer_detection key means "follow the platform default", which is shadow today and is expected to change once the detector has been measured;
  • an explicit value is stored as you sent it, including shadow, and the agent keeps it when the platform default moves.

So if you want an agent to observe and never do anything else, send shadow rather than leaving the section out. The editor makes the same distinction: the first choice is Platform default, and Shadow beside it is the explicit one.

The setting applies when a call starts; saving it never changes a call already in progress.

shadow is the platform default on purpose. It is the only setting that measures how often the verdict would have been wrong, on real calls in real languages, while being unable to end any of them.

On this page