---
title: "Annotate a call - note, flag or mark as handled"
method: PATCH
path: "/v1/calls/{id}"
---

# Annotate a call - note, flag or mark as handled

`PATCH /v1/calls/{id}`

## Request

- Base URL: `https://volai.cz`
- URL: `https://volai.cz/v1/calls/{id}`
- Auth: HTTP bearer

The account owner's own annotation on a call - a note, flagging the agent with a reason, and manual "handled". At least one of the three fields must be present. `note` (`string` or `null`, max 2000 characters) - `null` clears the note. `flag` (`{reason}` max 500 characters, or `null`) - setting a flag CLEARS `handledAt` (a flagged call is no longer "handled"); `flag: null` clears the flag without changing `handled`. `handled` (`boolean`) - `true` sets `handledAt` to now, `false` resets it to `null`. `flag` (non-null) and `handled: true` cannot both be set in the SAME request (400 `validation`) - flagging and confirmed handling are mutually exclusive; send them in two calls.

## Path parameters

- `id` string, required

## Request body

- object
  - `note` string, nullable: Private account note about the call. null or an empty string clears it; omission preserves it.
  - `flag` object, nullable: Mark the call for review with a reason; null removes the flag. Flagging clears its handled state and cannot be combined with handled: true.
    - `reason` string, required: Why the call needs review; must not be blank.
  - `handled` boolean: true marks the call handled; false clears the handled timestamp. Omission leaves the state unchanged.
  - `rating` object: One-click call rating, same as the portal card. A fresh rating overwrites an earlier one; recorded with source "api". Omission leaves it unchanged.
    - `value` 'good' | 'bad', required: good or bad.
    - `reason` 'misunderstood' | 'wrong_answer' | 'silence' | 'hung_up_early' | 'other': Reason for a bad rating: misunderstood, wrong_answer, silence, hung_up_early or other. Only accepted together with value: "bad".
    - `note` string: Optional free-text note for a bad rating. Only accepted together with value: "bad".

## Response `200`

Call detail

- object
  - `call` object, required
    - `id` string, required
    - `direction` 'in' | 'out', required
    - `from` string, required
    - `to` string, required
    - `status` 'initiated' | 'ringing' | 'in_progress' | 'completed' | 'failed' | 'missed' | 'no_answer', required
    - `startedAt` number, required
    - `durationSecs` number
    - `priceHal` number
    - `agentId` string
    - `callbackOf` object
      - `callId` string, required
      - `taskId` string
      - `taskItemId` string
    - `source` 'api' | 'mcp' | 'portal' | 'inbound', required
    - `kind` 'agent' | 'bridge' | 'relay' | 'inbound' | 'transfer' | 'sip' | 'ring'
    - `ring` object: Present only on a ring attempt (`kind` is `ring`): how long ringing was requested, how the attempt ended and how long the target rang. `outcome` and `rangSecs` are absent while the attempt is still running.
      - `requestedSecs` integer, required: Seconds of ringing that were requested for this attempt: the ringSecs of the request, or 10 when it was omitted.
      - `outcome` 'rang' | 'ended_early' | 'answered' | 'unknown': How a ring attempt ended. `rang`: the target rang and volai hung up when the requested time ran out. `ended_early`: the call ended on its own before volai hung up (the target declined it, was busy or unreachable). `answered`: the target picked up and volai hung up at once; the connected seconds are billed on top at the outbound call rate. `unknown`: the result could not be determined (for example the call never showed up as ringing) and is settled later from the carrier's call record. A gate that opens when it sees the call usually ends as `rang` or `ended_early`; `answered` is the exception.
      - `rangSecs` number: How long the target rang, in seconds. First volai's own measurement, replaced by the carrier's figure once its call record has been processed. The carrier counts the ringing from the moment the order is placed, so its figure is usually 1 to 3 seconds longer than the requested `ringSecs`.
    - `answeredBy` 'human' | 'voicemail' | 'ivr' | 'unknown': Outbound calls only: `human`, `voicemail`, `ivr` (an automated voice menu answered; the engine hangs up), or `unknown` when the engine reached no decision or the transcript is missing. Inbound calls omit the field, because a brief caller reply is not a voicemail.
    - `voicemailReason` 'phrase' | 'long_monologue' | 'beep' | 'human_reply' | 'ivr_phrase' | 'digits'
    - `voicemailMessageLeft` boolean
    - `optOutRequested` true: Calls on the built-in engine, in either direction: the remote participant explicitly asked not to be called again (for example "už mi nevolejte" / "do not call me"), even in the last seconds after the farewell. Present only as `true`; this triggers protection of the remote participant's number in the account's do-not-call list (outbound to, inbound from).
    - `voicemailDetectedAtSecs` number
    - `endReason` 'completed' | 'no_answer' | 'busy' | 'rejected' | 'capacity' | 'blocked' | 'loop_guard' | 'credit_blocked' | 'suspended' | 'failed' | 'engine_error' | 'caller_loop'
    - `workflowPath` object[]: Path of the call through the agent's workflow nodes, if any. Written only by volai's own voice engine's post-call (provider: "engine"); a call on an agent hosted on ElevenLabs never carries this field, even when the agent has phases and the call went through them. Also absent when the agent has no workflow configured.
      - `node` string, required
      - `label` string, required
      - `viaEdge` string, nullable, required: Id of the edge this step entered through; null for the entry step.
      - `turnIndex` number, required
      - `at` number, required: Unix ms timestamp of this step.
      - `enterPhraseSpoken` boolean, required
    - `data` object
    - `hasRecording` boolean
    - `note` string, nullable, required
    - `flag` object, nullable, required
      - `reason` string, required
      - `flaggedAt` number, required
    - `handledAt` number, nullable, required
    - `goal` object, nullable, required
      - `result` 'success' | 'failure' | 'unknown', required
      - `rationale` string, nullable, required
      - `reason` 'no_caller_speech' | 'evaluation_error', nullable, required: Why the result is `unknown` - `no_caller_speech` (the caller never spoke) or `evaluation_error` (the evaluation itself failed); `null` for a genuinely unclear result or an older record without this field.
    - `hasConversation` boolean, nullable, required: Whether the call had an actual conversation with the caller. `false` means reliably no conversation (not connected, voicemail, a voice menu answered, or the engine explicitly reported the caller never spoke). `null` means it cannot be determined (an older record with neither a transcript nor the signal).
    - `rating` object, nullable, required: One-click call rating from the portal, the post-call e-mail, PATCH /v1/calls/{id} or MCP annotate_call. null means not rated yet.
      - `value` 'good' | 'bad', required
      - `reason` 'misunderstood' | 'wrong_answer' | 'silence' | 'hung_up_early' | 'other'
      - `note` string
      - `at` number, required
      - `source` 'portal' | 'email' | 'api' | 'mcp', required
    - `missedSms` object: Present once the missed-call SMS was actually sent (or queued and later sent during quiet hours) for this call.
      - `sentAt` number, required
      - `messageId` string, required
    - `transcript` object[]
      - `role` 'agent' | 'caller', required
      - `message` string, required
      - `timeInCallSecs` number
      - `interrupted` boolean: true on an agent line the caller interrupted, so they did not hear all of it; message still holds the full planned text. Absent on every other line, and on calls handled by ElevenLabs.
    - `summary` string
    - `engine` object: Facts the volai engine measured about the call. Present only on calls handled by an engine build from 15 Oct 2026 on; absent on older engine calls and on agents hosted on ElevenLabs. The same block is in the call.* webhook payload.
      - `protectedUtterances` object[], required: Utterances of the person who answered that the engine dropped on purpose during the greeting window (a "Hello?" or their name while the first line played, or any reply inside firstMessageProtectSecs). They are not in the transcript and the agent did not react to them, so an agent-only transcript with a non-empty list means the person did answer and speak. At most 10 items, in call order; [] when nothing was dropped.
        - `timeInCallSecs` number: Seek position of the start of the utterance in the recording, in seconds (same convention as transcript turns). Absent when the engine does not know the start.
        - `atSecs` number, required: When the engine finished hearing the utterance, in seconds from the start of the call. Always present.
        - `message` string, required: What the person who answered said, up to 200 characters. Not part of the transcript; the agent never heard it.
        - `words` number, required: Word count of the full utterance, counted before the 200-character cut.
        - `reason` string, required: Why the engine dropped it: first_message_protect (inside firstMessageProtectSecs), greeting_courtesy (a courtesy word such as "Hello?" while the first line played) or greeting_identification (the person stating their name). Open list; tolerate unknown values.
      - `protectedUtterancesCount` integer, required: Number of all dropped utterances, including any beyond the 10 listed.
  - `stillRunning` boolean

## Other responses

- `400`: Error codes: validation (request).
- `401`: Error codes: unauthorized (request).
- `403`: Error codes: account_suspended (account).
- `404`: Error codes: call_not_found (account).
- `429`: Error codes: rate_limited (busy).
- `500`: Error codes: internal_error (service).

---

[API](https://skmtc.dev/volai/apis/volai-rest-api.md) · [All operations](https://skmtc.dev/volai/apis/volai-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/volai/apis/volai-rest-api/revisions/12029b05e2fa?raw)
