Call Trace API Documentation
Submit call recordings from your own system, receive an immediate 202 response, and get the final transcript and evaluation through signed webhooks.
Create an API key
Open Admin > Integrations and create a key with the analysis:write scope.
Submit a file URL
Send an HTTPS fileUrl to POST /api/v1/analyses. The API responds with 202 Accepted.
Receive the result
Subscribe to analysis.completed and analysis.failed to get the final evaluation.
Verify the signature
Validate X-CallTrace-Signature before accepting any webhook payload.
API keys
Create a token in the admin panel
Only admins can create integration API keys. Keys are scoped, can expire, can be revoked, and are independent from employee sessions.
Open Integrations
Sign in as an admin and open Admin > Integrations > API Keys.
Name the key
Use a name that identifies the external system, environment, and owner.
Choose scopes
Select only the permissions that system needs. Most upload integrations need analysis:write and analysis:read.
Set lifetime
Choose an expiration date for temporary access or leave it permanent for long-running service integrations.
Copy once
The full ct_live_ token is shown only once. After closing the panel, only the prefix remains visible.
Token format
ct_live_8Lk3...full-secret-valueStore the full token in your external system secret manager. Call Trace stores only a hash and shows only the prefix after creation.
Copy it immediately
The token is displayed only once when it is created. If it is lost, revoke that key and create a new one. Do not send API keys through email, chat, or browser console logs.
Authentication
Use a scoped integration key
Integration API keys are separate from employee sessions. Send the key in the Authorization header and grant only the scopes your system needs.
Header
Authorization: Bearer ct_live_...
Content-Type: application/jsonScopes
analysis:write creates analyses.
analysis:read reads status and results.
webhooks:write manages webhook settings.
Permissions
What each scope allows
Scopes are additive. Start with the smallest set that supports the integration and add more only when a specific endpoint requires it.
analysis:writeanalysis:readcalls:readstorage:writewebhooks:readwebhooks:writeRecommended minimum
For the standard flow, create one token with analysis:write and analysis:read. Webhooks are configured by an admin in the UI, so the external sender usually does not need webhooks:write.
Token lifetime
Expiration, permanent access, and rotation
Each API key has its own lifetime. A token can be permanent, expire automatically, or be revoked manually.
Permanent token
expiresAt is empty. The token stays active until an admin revokes it. Use this only for trusted backend-to-backend integrations with a rotation process.
Expiring token
expiresAt is set. After that date the API returns 401 and no new requests are accepted. Use this for tests, contractors, or temporary access.
Revoked token
revokedAt is set immediately when an admin revokes the key. Revocation is instant and cannot be undone; create a new key instead.
Rotation
Create a new key, deploy it in the external system, confirm last used updates, then revoke the old key. This avoids downtime.
API reference
Create an analysis
The request stores the source file and starts background processing. Your client should not wait for transcription or evaluation to finish in this request.
Create analysis
Returns 202 Accepted and queues processing.
/api/v1/analysesRequest
POST /api/v1/analyses
Authorization: Bearer ct_live_...
Content-Type: application/json
{
"fileUrl": "https://example.com/calls/call-123.mp3",
"fileName": "call-123.mp3",
"language": "auto",
"externalId": "crm-call-123",
"idempotencyKey": "crm-call-123",
"metadata": {
"customerId": "cust_42",
"source": "crm"
}
}Response
HTTP/1.1 202 Accepted
Content-Type: application/json
{
"analysisId": "6f6af4bb-4f23-4f3f-a87e-85f2f8322dc1",
"status": "queued",
"externalId": "crm-call-123"
}fileUrlfileNameagentIdqueueIdlanguageexternalIdidempotencyKeymetadataFile URL requirements
fileUrl must use HTTPS. Private, localhost, and internal IP ranges are rejected, including redirects into private networks. Files are downloaded with timeouts, content-type checks, and maximum size limits before processing.
API reference
Read status and results
Use this endpoint when your system needs to reconcile state or recover after missed webhook deliveries.
Get analysis
Returns the latest job status and available result fields.
/api/v1/analyses/{analysisId}{
"analysisId": "6f6af4bb-4f23-4f3f-a87e-85f2f8322dc1",
"status": "completed",
"externalId": "crm-call-123",
"call": {
"durationSeconds": 184,
"agentId": "agent-uuid",
"queueId": "queue-uuid"
},
"evaluation": {
"score": 82,
"summary": "The agent followed the script and resolved the main request.",
"criteria": [
{ "name": "Greeting", "score": 10, "maxScore": 10 },
{ "name": "Need discovery", "score": 18, "maxScore": 20 }
]
},
"transcript": {
"language": "en",
"segments": [
{ "speaker": "agent", "text": "Good afternoon, how can I help?" }
]
},
"metadata": {
"customerId": "cust_42"
},
"error": null
}queuedThe request is accepted and waiting for processing.
processingThe file is being downloaded, transcribed, and evaluated.
completedThe evaluation, transcript, and call summary are available.
failedProcessing failed after retries. Details are returned in error.
API reference
List analyses
Search by status, externalId, agent, queue, or date range. Use this for back-office reconciliation and support tooling.
List analyses
Returns paginated analyses for the integration company.
/api/v1/analysesGET /api/v1/analyses?status=completed&externalId=crm-call-123
Authorization: Bearer ct_live_...Webhooks
Webhook payload
Configure webhook endpoints in Admin > Integrations. Payloads include the information needed to store the assessment result in your own system.
{
"deliveryId": "whd_01jz8n0f8ck2bk0smq7h2x1t0r",
"event": "analysis.completed",
"occurredAt": "2026-06-19T12:00:00.000Z",
"analysisId": "6f6af4bb-4f23-4f3f-a87e-85f2f8322dc1",
"externalId": "crm-call-123",
"call": {
"status": "completed",
"durationSeconds": 184,
"recordingUrl": "https://storage.example.com/audio/call-123.mp3"
},
"evaluation": {
"score": 82,
"summary": "The agent followed the script and resolved the main request.",
"criteria": [
{ "name": "Greeting", "score": 10, "maxScore": 10 }
]
},
"transcript": {
"language": "en",
"segments": [
{ "speaker": "agent", "text": "Good afternoon, how can I help?" }
]
},
"metadata": {
"customerId": "cust_42"
},
"error": null
}Script text is not included by default. Use deliveryId for deduplication and analysisId plus event when storing state transitions.
Webhooks
Delivery and retries
Call Trace stores every delivery attempt and retries failed deliveries so results are not lost on timeout or temporary customer outages.
Success
Any 2xx response marks the delivery as delivered.
Retry policy
Initial attempt plus 3 retries with backoff, for example 10 seconds, 1 minute, and 5 minutes.
History
Each attempt stores status, response code, error message, timestamps, and payload snapshot.
Webhooks
Verify signatures
Every webhook is signed with the endpoint secret shown once when the endpoint is created.
Headers
X-CallTrace-Timestamp
X-CallTrace-Delivery
X-CallTrace-Signature
The signed value is timestamp + "." + raw request body.
const expected = crypto
.createHmac("sha256", process.env.CALL_TRACE_WEBHOOK_SECRET)
.update(timestamp + "." + rawRequestBody)
.digest("hex");
const valid = signature === "sha256=" + expected;Examples
Create an analysis from your language
All examples use the same stable endpoint and expect the API key in CALL_TRACE_API_KEY.
JavaScript
const response = await fetch("https://your-domain.com/api/v1/analyses", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CALL_TRACE_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
fileUrl: "https://example.com/calls/call-123.mp3",
fileName: "call-123.mp3",
language: "auto",
externalId: "crm-call-123",
idempotencyKey: "crm-call-123"
})
});
const analysis = await response.json();
console.log(analysis.analysisId);Go
payload := strings.NewReader(`{
"fileUrl": "https://example.com/calls/call-123.mp3",
"fileName": "call-123.mp3",
"language": "auto",
"externalId": "crm-call-123",
"idempotencyKey": "crm-call-123"
}`)
req, err := http.NewRequest("POST", "https://your-domain.com/api/v1/analyses", payload)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("CALL_TRACE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)Java
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://your-domain.com/api/v1/analyses"))
.header("Authorization", "Bearer " + System.getenv("CALL_TRACE_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("""
{
"fileUrl": "https://example.com/calls/call-123.mp3",
"fileName": "call-123.mp3",
"language": "auto",
"externalId": "crm-call-123",
"idempotencyKey": "crm-call-123"
}
"""))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());PHP
$payload = [
"fileUrl" => "https://example.com/calls/call-123.mp3",
"fileName" => "call-123.mp3",
"language" => "auto",
"externalId" => "crm-call-123",
"idempotencyKey" => "crm-call-123"
];
$ch = curl_init("https://your-domain.com/api/v1/analyses");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("CALL_TRACE_API_KEY"),
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($ch);Errors
Error handling
Use idempotency keys for safe retries. Validation errors are returned synchronously. Processing errors are returned by status endpoint and analysis.failed webhooks.
400Invalid JSON, missing fileUrl, unsupported content type, or invalid metadata.
401Missing, expired, revoked, or malformed API key.
403The API key does not include the required scope.
409The same idempotency key was already used for another payload.
422The file URL is blocked, private, not HTTPS, too large, or not reachable.
500Temporary server error. Retry with the same idempotency key.