curl --request GET \
--url https://api.erdo.ai/v1/voice/calls/{callID} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.erdo.ai/v1/voice/calls/{callID}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.erdo.ai/v1/voice/calls/{callID}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.erdo.ai/v1/voice/calls/{callID}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.erdo.ai/v1/voice/calls/{callID}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.erdo.ai/v1/voice/calls/{callID}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/voice/calls/{callID}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"call_id": "<string>",
"call_successful": "<string>",
"canonical_lead_id": "<string>",
"consent_basis": "<string>",
"contact": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"phone": "<string>"
},
"created_at": "2023-11-07T05:31:56Z",
"dial_outcome": "<string>",
"direction": "<string>",
"duration_seconds": -1,
"ended_at": "2023-11-07T05:31:56Z",
"from_number": "<string>",
"has_transcript": true,
"instructions": "<string>",
"lead_reference": "<string>",
"lead_saved_at": "2023-11-07T05:31:56Z",
"lead_status": "<string>",
"placed_by_name": "<string>",
"provider_agent_id": "<string>",
"recipient_name": "<string>",
"status": "<string>",
"to_number": "<string>",
"transcript_json": {},
"transcript_summary": "<string>",
"transcript_text": "<string>",
"transcript_turns": [
{
"at": "2023-11-07T05:31:56Z",
"role": "<string>",
"text": "<string>"
}
],
"turns": [
{
"after_knowledge_result": true,
"after_tool_result": true,
"cache_read_tokens": 123,
"input_tokens": 123,
"llm_override": "<string>",
"models": [
"<string>"
],
"output_tokens": 123,
"rescued": true,
"time_in_call_secs": 123,
"tool_calls": [
"<string>"
],
"tool_result_chars": 123,
"ttfb_seconds": 123,
"turn_index": 123
}
]
}{
"code": "not_found",
"details": {},
"message": "<string>"
}GetVoiceCallAPI mirrors erdo_voice_call_get, returning the displayable
`transcript_turns`, the rendered and raw transcript, the structured per-turn LLM metrics (`turns`), and the lead the call produced (`contact`, `lead_status`, `lead_saved_at`, `canonical_lead_id`, `lead_reference` — see the list endpoint). The call id is a provider conversation id (“conv_…”), which travels in the path.
curl --request GET \
--url https://api.erdo.ai/v1/voice/calls/{callID} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.erdo.ai/v1/voice/calls/{callID}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.erdo.ai/v1/voice/calls/{callID}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.erdo.ai/v1/voice/calls/{callID}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.erdo.ai/v1/voice/calls/{callID}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.erdo.ai/v1/voice/calls/{callID}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.erdo.ai/v1/voice/calls/{callID}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"call_id": "<string>",
"call_successful": "<string>",
"canonical_lead_id": "<string>",
"consent_basis": "<string>",
"contact": {
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"phone": "<string>"
},
"created_at": "2023-11-07T05:31:56Z",
"dial_outcome": "<string>",
"direction": "<string>",
"duration_seconds": -1,
"ended_at": "2023-11-07T05:31:56Z",
"from_number": "<string>",
"has_transcript": true,
"instructions": "<string>",
"lead_reference": "<string>",
"lead_saved_at": "2023-11-07T05:31:56Z",
"lead_status": "<string>",
"placed_by_name": "<string>",
"provider_agent_id": "<string>",
"recipient_name": "<string>",
"status": "<string>",
"to_number": "<string>",
"transcript_json": {},
"transcript_summary": "<string>",
"transcript_text": "<string>",
"transcript_turns": [
{
"at": "2023-11-07T05:31:56Z",
"role": "<string>",
"text": "<string>"
}
],
"turns": [
{
"after_knowledge_result": true,
"after_tool_result": true,
"cache_read_tokens": 123,
"input_tokens": 123,
"llm_override": "<string>",
"models": [
"<string>"
],
"output_tokens": 123,
"rescued": true,
"time_in_call_secs": 123,
"tool_calls": [
"<string>"
],
"tool_result_chars": 123,
"ttfb_seconds": 123,
"turn_index": 123
}
]
}{
"code": "not_found",
"details": {},
"message": "<string>"
}Authorizations
An Erdo API key (erdo_api_...) or scoped token (erdo_token_...).
Path Parameters
Response
Success response
to, in both forms — the same two fields, with the same empty cases, as the list.
ones the leads dataset has columns for; there is deliberately no company, because nothing downstream would use it.
Show child attributes
Show child attributes
no_answer | busy | failed | canceled, where canceled means the lead was never dialled because the person did not answer their own phone or did not press 1. An AI call whose conversation the provider reported carries answered when the other side spoke, voicemail when a voicemail box picked up (the agent's voicemail detection fired, or everything the other side said was a recording phrase), or no_answer when the line connected and nobody said anything, or said one short fragment and then nothing until the agent hung up — all three have status `completed`, so this is the field that tells them apart. An AI call that never connected carries no_answer | busy | failed | canceled, matching its status; one closed because no ending ever arrived carries Erdo's best guess. Empty on inbound calls, on any call that has not ended yet, and on AI calls that connected before 2026-10-09. It is separate from `status`, which is about the record: a call can be `completed` with a `no_answer` outcome, which is a call that happened and reached nobody.
-2147483648 <= x <= 2147483647from the Call button on a lead carries the name of whoever pressed it, and every AI-placed and inbound call leaves it empty. It is what turns a row in the list into "Called by Ana".
verbatim. Null when no transcript was captured.
("user: ...\nagent: ..."), shared with the widget read surface.
([{role, text, at}]), so a consumer displaying a call renders one shape instead of re-parsing the provider payload. Built by the renderer TranscriptText is built on, so the two can never disagree. On an INBOUND call `at` is the call's start plus the turn's offset into it; an outbound call's turns carry no `at`, because its record starts when the provider accepted the request, before the call rang or was answered. Always present (empty when nothing rendered) — the same field the widget conversation read returns.
Show child attributes
Show child attributes
same projection the widget conversation read returns.
Show child attributes
Show child attributes

