curl --request POST \
--url https://your-instance.example.com/api/calls/ingest \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"direction": "outbound",
"fromNumber": "+14165550100",
"toNumber": "+14165550199",
"duration": 154,
"status": "completed",
"transcript": [
{
"role": "agent",
"message": "Hi, this is Alex from Example Corp.",
"timestamp": 0.5
},
{
"role": "user",
"message": "Hey Alex.",
"timestamp": 2.8
}
],
"summary": "Confirmed appointment for Friday at 2pm.",
"recording": "https://vendor.example/recordings/abc123.mp3",
"externalCallId": "vendor-call-abc123",
"agentName": "Alex (Example Agent)",
"callStartTime": "2026-05-15T09:10:00.000Z"
}
'import requests
url = "https://your-instance.example.com/api/calls/ingest"
payload = {
"direction": "outbound",
"fromNumber": "+14165550100",
"toNumber": "+14165550199",
"duration": 154,
"status": "completed",
"transcript": [
{
"role": "agent",
"message": "Hi, this is Alex from Example Corp.",
"timestamp": 0.5
},
{
"role": "user",
"message": "Hey Alex.",
"timestamp": 2.8
}
],
"summary": "Confirmed appointment for Friday at 2pm.",
"recording": "https://vendor.example/recordings/abc123.mp3",
"externalCallId": "vendor-call-abc123",
"agentName": "Alex (Example Agent)",
"callStartTime": "2026-05-15T09:10:00.000Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
direction: 'outbound',
fromNumber: '+14165550100',
toNumber: '+14165550199',
duration: 154,
status: 'completed',
transcript: [
{role: 'agent', message: 'Hi, this is Alex from Example Corp.', timestamp: 0.5},
{role: 'user', message: 'Hey Alex.', timestamp: 2.8}
],
summary: 'Confirmed appointment for Friday at 2pm.',
recording: 'https://vendor.example/recordings/abc123.mp3',
externalCallId: 'vendor-call-abc123',
agentName: 'Alex (Example Agent)',
callStartTime: '2026-05-15T09:10:00.000Z'
})
};
fetch('https://your-instance.example.com/api/calls/ingest', 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://your-instance.example.com/api/calls/ingest",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'direction' => 'outbound',
'fromNumber' => '+14165550100',
'toNumber' => '+14165550199',
'duration' => 154,
'status' => 'completed',
'transcript' => [
[
'role' => 'agent',
'message' => 'Hi, this is Alex from Example Corp.',
'timestamp' => 0.5
],
[
'role' => 'user',
'message' => 'Hey Alex.',
'timestamp' => 2.8
]
],
'summary' => 'Confirmed appointment for Friday at 2pm.',
'recording' => 'https://vendor.example/recordings/abc123.mp3',
'externalCallId' => 'vendor-call-abc123',
'agentName' => 'Alex (Example Agent)',
'callStartTime' => '2026-05-15T09:10:00.000Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your-instance.example.com/api/calls/ingest"
payload := strings.NewReader("{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your-instance.example.com/api/calls/ingest")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your-instance.example.com/api/calls/ingest")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"deduplicated": true,
"callId": "64f0a1b2c3d4e5f6a7b8cb00"
}{
"success": true,
"callId": "64f0a1b2c3d4e5f6a7b8cb00",
"contactId": "64f0a1b2c3d4e5f6a7b8c9d0",
"conversationId": "64f0a1b2c3d4e5f6a7b8c9e0"
}{
"success": false,
"error": "Missing required field: transcript"
}{
"success": false,
"error": "Missing required field: transcript"
}{
"success": false,
"error": "Missing required field: transcript"
}Ingest an externally completed call record
Public ingestion endpoint for vendor-agnostic external calling systems to push completed call records into Tether. Authenticated via a user-scoped API key (Bearer token from GET /api/user/api-key) — does NOT use the standard JWT auth. Creates the same Contact / Conversation / Call / recording artifacts as the ElevenLabs post-call webhook. Idempotent on externalCallId. Uploads recordings (base64 MP3 or URL) to R2. Emits call_initiated, call_ended, call_recording_ready, and fetch_messages socket events.
curl --request POST \
--url https://your-instance.example.com/api/calls/ingest \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"direction": "outbound",
"fromNumber": "+14165550100",
"toNumber": "+14165550199",
"duration": 154,
"status": "completed",
"transcript": [
{
"role": "agent",
"message": "Hi, this is Alex from Example Corp.",
"timestamp": 0.5
},
{
"role": "user",
"message": "Hey Alex.",
"timestamp": 2.8
}
],
"summary": "Confirmed appointment for Friday at 2pm.",
"recording": "https://vendor.example/recordings/abc123.mp3",
"externalCallId": "vendor-call-abc123",
"agentName": "Alex (Example Agent)",
"callStartTime": "2026-05-15T09:10:00.000Z"
}
'import requests
url = "https://your-instance.example.com/api/calls/ingest"
payload = {
"direction": "outbound",
"fromNumber": "+14165550100",
"toNumber": "+14165550199",
"duration": 154,
"status": "completed",
"transcript": [
{
"role": "agent",
"message": "Hi, this is Alex from Example Corp.",
"timestamp": 0.5
},
{
"role": "user",
"message": "Hey Alex.",
"timestamp": 2.8
}
],
"summary": "Confirmed appointment for Friday at 2pm.",
"recording": "https://vendor.example/recordings/abc123.mp3",
"externalCallId": "vendor-call-abc123",
"agentName": "Alex (Example Agent)",
"callStartTime": "2026-05-15T09:10:00.000Z"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
direction: 'outbound',
fromNumber: '+14165550100',
toNumber: '+14165550199',
duration: 154,
status: 'completed',
transcript: [
{role: 'agent', message: 'Hi, this is Alex from Example Corp.', timestamp: 0.5},
{role: 'user', message: 'Hey Alex.', timestamp: 2.8}
],
summary: 'Confirmed appointment for Friday at 2pm.',
recording: 'https://vendor.example/recordings/abc123.mp3',
externalCallId: 'vendor-call-abc123',
agentName: 'Alex (Example Agent)',
callStartTime: '2026-05-15T09:10:00.000Z'
})
};
fetch('https://your-instance.example.com/api/calls/ingest', 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://your-instance.example.com/api/calls/ingest",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'direction' => 'outbound',
'fromNumber' => '+14165550100',
'toNumber' => '+14165550199',
'duration' => 154,
'status' => 'completed',
'transcript' => [
[
'role' => 'agent',
'message' => 'Hi, this is Alex from Example Corp.',
'timestamp' => 0.5
],
[
'role' => 'user',
'message' => 'Hey Alex.',
'timestamp' => 2.8
]
],
'summary' => 'Confirmed appointment for Friday at 2pm.',
'recording' => 'https://vendor.example/recordings/abc123.mp3',
'externalCallId' => 'vendor-call-abc123',
'agentName' => 'Alex (Example Agent)',
'callStartTime' => '2026-05-15T09:10:00.000Z'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your-instance.example.com/api/calls/ingest"
payload := strings.NewReader("{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your-instance.example.com/api/calls/ingest")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your-instance.example.com/api/calls/ingest")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"direction\": \"outbound\",\n \"fromNumber\": \"+14165550100\",\n \"toNumber\": \"+14165550199\",\n \"duration\": 154,\n \"status\": \"completed\",\n \"transcript\": [\n {\n \"role\": \"agent\",\n \"message\": \"Hi, this is Alex from Example Corp.\",\n \"timestamp\": 0.5\n },\n {\n \"role\": \"user\",\n \"message\": \"Hey Alex.\",\n \"timestamp\": 2.8\n }\n ],\n \"summary\": \"Confirmed appointment for Friday at 2pm.\",\n \"recording\": \"https://vendor.example/recordings/abc123.mp3\",\n \"externalCallId\": \"vendor-call-abc123\",\n \"agentName\": \"Alex (Example Agent)\",\n \"callStartTime\": \"2026-05-15T09:10:00.000Z\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"deduplicated": true,
"callId": "64f0a1b2c3d4e5f6a7b8cb00"
}{
"success": true,
"callId": "64f0a1b2c3d4e5f6a7b8cb00",
"contactId": "64f0a1b2c3d4e5f6a7b8c9d0",
"conversationId": "64f0a1b2c3d4e5f6a7b8c9e0"
}{
"success": false,
"error": "Missing required field: transcript"
}{
"success": false,
"error": "Missing required field: transcript"
}{
"success": false,
"error": "Missing required field: transcript"
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Vendor-agnostic completed-call payload. transcript, summary, and recording are required when status === "completed".
inbound, outbound E.164 (e.g. +14155551234).
E.164.
Call duration in seconds (non-negative).
completed, no-answer, busy, failed Structured transcript (required when status is "completed"). Plain text is not accepted.
Show child attributes
Show child attributes
Required when status is "completed".
Required when status is "completed". Base64-encoded MP3 (optionally with a data:audio/...;base64, prefix) or a publicly downloadable URL.
Optional external identifier used for idempotency.
Name of the external AI agent.
Arbitrary vendor metadata (stored on providerMetadata.metadata).
Used to backfill placeholder contact name when the contact was newly created as "Unknown Caller".
ISO start time; defaults to now.