> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.getdial.ai/api-reference/rest-api/calls/stop-call/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.getdial.ai/_mcp/server. # Stop a call POST https://api.getdial.ai/api/v1/calls/{id}/stop Ends a call that hasn't finished yet, inbound or outbound. Takes no request body. What happens depends on how far the call has got: - **`Queued` or `Ringing`** — the call is cancelled before anyone answers. It never connects, nothing is charged, and it ends with `terminationType: "canceled"`. - **`In-Progress`** — the call is hung up. It ends with `terminationType: "completed"`, and its `status.label` reads `Completed (Cancelled)`. If the callee answers in the instant before the stop takes effect, the call is hung up rather than cancelled, so it ends as `completed` and is billed for the seconds it was connected. **The call isn't over when this returns.** The response carries the call with `status.cancelRequested` and `status.cancelPending` set to `true`; `status.state` still shows the state the call was in. The call reaches `Terminated` a moment later, which emits [`call.status_changed`](/api-reference/events/call-status-changed) and [`call.ended`](/api-reference/events/call-ended) with `canceled: true`. Wait on `call.ended` to know it's done. Once the call has ended, stopping it returns `400`. With [Self-Hosted](/documentation/platform/self-hosted), Dial opens the socket to your server only once an outbound call is answered, so stopping a `Queued` or `Ringing` call means your server is never contacted. Reference: https://docs.getdial.ai/api-reference/rest-api/calls/stop-call ## Authentication - `Authorization` header (bearer token, required) — Your Dial API key, sent as `Authorization: Bearer sk_live_...` ## Request ### Path parameters - `id` (string, required) ## Response ### 200 The stop was accepted. The returned call has `status.cancelPending: true` until it reaches `Terminated`. - `call` (Call, optional) ## Errors ### 400 Bad Request Error The call has already ended (`Terminated`), so there is nothing to stop. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 401 Unauthorized Error Missing or invalid API key. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 404 Not Found Error The requested resource was not found on this account. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ### 502 Bad Gateway Error The call couldn't be ended this time. It may still be live — retry the request. - `error` (ErrorError, optional) — An error message, or a validation-error object for 400 responses. ## Types ### Call - `id` (string, optional) - `phoneNumberId` (string, optional) - `from` (string, optional) - `to` (string, optional) - `direction` (enum, optional) - Allowed values: `inbound`, `outbound` - `status` (CallStatus, optional) — Where the call is in its lifecycle. Moves forward only: `Queued` → `Ringing` → `In-Progress` → `Terminated`, though a call can skip states (see [`call.status_changed`](/api-reference/events/call-status-changed)). - `duration` (integer, optional) — Call duration in seconds. - `transcript` (string, optional, nullable) — Transcript text, available after the call ends. - `transcriptTurns` (list of TranscriptTurn, optional, nullable) — The same conversation as `transcript`, split into timed turns. Use it to analyse pacing, above all the silence between turns, which the flat `transcript` string gives you no way to see. Ordered by `startMs`. Null whenever `transcript` is null, on calls that completed before Dial began recording turn timing, and on any call whose turns could not ALL be timed and attributed. The array is never partial: a transcript missing a turn would report the silence around it as one long pause that never happened, so Dial publishes every turn or none. - `voiceRuntime` (enum, optional) — Which voice runtime served this call. `managed` — Dial's voice agent ran the call; this includes Self-Hosted's LLM mode, where Dial still handles the audio and only the LLM is yours. `self-hosted-audio` — the call's raw audio was streamed to your own server, so Dial never heard the conversation and produced no transcript or recording for it — `transcript` is always `null`. See the [Self-Hosted guide](/documentation/platform/self-hosted). Null on calls that predate this field. - Allowed values: `managed`, `self-hosted-audio` - `failureReason` (enum, optional) — Why the call failed, when Dial knows. Set only on a call whose `terminationType` is `failed`; `null` on every other call, and on a failed call whose cause Dial can't name. Today every reason comes from a Self-Hosted **audio** target. `self_hosted_key_rejected`: Pipecat Cloud rejected your public key; save the right key in Self-Hosted settings. `self_hosted_agent_not_found`: no Pipecat Cloud agent has the configured agent name. `self_hosted_at_capacity`: your Pipecat Cloud agent had no capacity for another session (raise `max_agents`). `self_hosted_unreachable`: Dial could not reach your target, either your `wsUrl` server didn't accept the connection or Pipecat Cloud didn't answer. More values may be added; treat an unknown value like `null`. - Allowed values: `self_hosted_key_rejected`, `self_hosted_agent_not_found`, `self_hosted_at_capacity`, `self_hosted_unreachable` - `instruction` (string, optional, nullable) — The system prompt the AI voice agent ran with for this call. For outbound calls, this is the `outboundInstruction` passed to `POST /api/v1/calls`. For inbound calls, this is a snapshot of the destination number's `inboundInstruction` at the moment the call was answered — later edits to the number do not retroactively change it. - `transferTo` (string, optional, nullable) — Forward-to number (E.164), or `null` when the call was not transferred or forwarded. For an outbound call, the `transferTo` requested when the call was placed (see `POST /api/v1/calls`). For an inbound call, the number's `forwardTo` at the moment the call arrived, when the call was forwarded instead of answered by the AI voice agent. - `transferredAt` (datetime, optional, nullable) — Timestamp of the moment the call was handed off to `transferTo` — the cold transfer on an outbound call, or the moment the forwarded phone answered on an inbound call — or `null` if no hand-off occurred. - `createdAt` (datetime, optional) ### ErrorError An error message, or a validation-error object for 400 responses. ### CallStatus Where the call is in its lifecycle. Moves forward only: `Queued` → `Ringing` → `In-Progress` → `Terminated`, though a call can skip states (see [`call.status_changed`](/api-reference/events/call-status-changed)). - `state` (enum, optional) — `Queued` — placed, not ringing yet. `Ringing` — the destination is ringing. `In-Progress` — answered and connected. `Terminated` — over; see `terminationType`. `Unknown` — a call record with no lifecycle information (rare). - Allowed values: `Queued`, `Ringing`, `In-Progress`, `Terminated`, `Unknown` - `terminationType` (enum, optional) — How the call ended. Set only when `state` is `Terminated`, null otherwise. `canceled` means the call was stopped before it was answered. - Allowed values: `completed`, `busy`, `no-answer`, `failed`, `canceled` - `cancelRequested` (boolean, optional) — `true` once [Stop a call](/api-reference/calls/stop-call) has been requested for this call (or it was ended from the dashboard). - `cancelPending` (boolean, optional) — `true` while a stop has been requested but the call hasn't reached `Terminated` yet. - `label` (string, optional) — A display string for the status, e.g. `Ringing`, `Completed`, or `Completed (Cancelled)` for an answered call that was stopped. Use `state` and `terminationType` for logic. ### TranscriptTurn One uninterrupted stretch of speech by one party, placed in time. Offsets count milliseconds into the call's audio, so the pause before a turn is `startMs` of that turn minus `endMs` of the one before it. The offsets are **approximate**. They are derived from per-word timings the voice runtime does not guarantee to be exact, and they measure audio time rather than wall-clock time. Ample for spotting a long silence, which is what they are for; not a basis for splitting tenths of a second. - `speaker` (enum, required) — Who spoke. `agent` is Dial's AI voice agent. `user` is the human on the other end, on inbound and outbound calls alike. `transfer_target` is the human the call was cold-transferred to (see `transferTo`), who is a different party from `user` and only ever appears on a call that was handed off. `agent` and `user` are the same values the Self-Hosted transcript frames use. - Allowed values: `agent`, `user`, `transfer_target` - `text` (string, required) — What was said during the turn. - `startMs` (integer, required) — Approximate milliseconds into the call's audio at which the turn began. - `endMs` (integer, required) — Approximate milliseconds into the call's audio at which the turn ended. Never smaller than `startMs`. ## Examples **Response** ```json { "call": { "id": "string", "phoneNumberId": "string", "from": "+14155550123", "to": "+14155559876", "direction": "inbound", "status": { "state": "Ringing", "terminationType": null, "cancelRequested": false, "cancelPending": false, "label": "Ringing" }, "duration": 0, "transcript": "string", "transcriptTurns": [ { "speaker": "agent", "text": "Hi, how can I help?", "startMs": 1200, "endMs": 2650 } ], "voiceRuntime": "managed", "failureReason": "self_hosted_key_rejected", "instruction": "string", "transferTo": "+14155550100", "transferredAt": "2024-01-15T09:30:00Z", "createdAt": "2024-01-15T09:30:00Z" } } ``` **SDK Code** ```python import requests url = "https://api.getdial.ai/api/v1/calls/id/stop" headers = {"Authorization": "Bearer "} response = requests.post(url, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.getdial.ai/api/v1/calls/id/stop'; const options = {method: 'POST', headers: {Authorization: 'Bearer '}}; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "net/http" "io" ) func main() { url := "https://api.getdial.ai/api/v1/calls/id/stop" req, _ := http.NewRequest("POST", url, nil) req.Header.Add("Authorization", "Bearer ") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.getdial.ai/api/v1/calls/id/stop") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.getdial.ai/api/v1/calls/id/stop") .header("Authorization", "Bearer ") .asString(); ``` ```php request('POST', 'https://api.getdial.ai/api/v1/calls/id/stop', [ 'headers' => [ 'Authorization' => 'Bearer ', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.getdial.ai/api/v1/calls/id/stop"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = ["Authorization": "Bearer "] let request = NSMutableURLRequest(url: NSURL(string: "https://api.getdial.ai/api/v1/calls/id/stop")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```