TwinTone API
TwinTone Video Agent API v1 - Overview
TwinTone’s Video Agent API empowers developers to seamlessly integrate AI-powered video agents into their applications, unlocking a new dimension of interactive and dynamic digital experiences. This REST API enables organizations to programmatically create and manage AI personas, facilitate real-time streaming conversations, and generate lifelike scripted speech.
Our documentation is optimized for efficiency, designed to fit within the context limits of leading large language models like Claude-3.5-Sonnet and ChatGPT-4o. Developers can use this guide to access a robust set of endpoints covering AI Agents videos, conversations, personas, and speech synthesis.
For best results, we recommend passing this documentation to an LLM for quick interpretation, but it's also structured for easy reading and navigation. Our API supports secure authentication via API keys, follows standard RESTful conventions, and includes detailed error handling for troubleshooting.
For API Keys, questions or support, feel free to reach out: James@twintone.ai
Base URL
API Base URL: https://api.twintone.ai/v1
Authentication
All API requests require an API key for authentication. Include the key in the x-api-key
header for every request.
Example: x-api-key: your-api-key
x-api-key: your-api-key
Endpoints
List all replicas
Get /replicas
Create a new replica
POST /replicas
Retrieve details of a specific replica
GET /replicas/:id
Delete a replica
DELETE /replicas/:id
Update replica’s name
PATCH /replicas/:id
List all generated videos
GET /videos
Generate a new video
POST /videos
Get a specific video
GET /videos/:id
Delete a replica
DELETE /replicas/:id
Update replica’s name
PATCH /replicas/:id
Delete a video
DELETE /videos/:id
Update video name
PATCH /videos/:id
List all conversations
GET /conversations
Create a new conversation
POST /conversations
Get a specific conversation
GET /conversations/:id
End a conversation
POST /conversations/:id
Delete a conversation
DELETE /conversations/:id
List all personas
/personas
Create a new persona
POST /personas
Get a specific video
GET /videos/:id
Get a specific persona
GET /personas/:id
Update a persona
PATCH /personas/:id
Delete a persona
DELETE /personas/:id
List all speech generations
GET /speech
Generate new speech
POST /speech
Get specific speech generation
GET /speech/:id
Delete speech
DELETE /speech/:id
Update speech name
PATCH /speech/:id
Error Handling
The API uses standard HTTP status codes to indicate the success or failure of requests. Responses include an error message for troubleshooting.
All errors return semantic HTTP status codes
50
2
status code likely indicates proxy error
Example error response:
{
"error": "InvalidRequest",
"message": "The provided parameters are invalid.",
"status": 400
}
Rate Limiting
Keep-alive connections are limited to 1 socket - can discuss if you have a use case that requires more.
Timeout set to 60 seconds for all requests
Exceeded Limits: Return status code
429
.
Endpoint details
Videos
POST /videos
Generate a new video using a replica.
POST /videos
Generate a new video using a replica.Request Body:
{
"replica_id": "string",
"script": "string",
"name": "string" // Optional
}
Response:
{
"id": "string",
"status": "processing",
"created_at": "string"
}
GET /videos/:id
Retrieve details of a specific video generation.
GET /videos/:id
Retrieve details of a specific video generation.Response:
{
"id": "string",
"name": "string",
"status": "processing|ready|failed", // Indicates the current processing state
"url": "string", // Present only if status is "ready"
"created_at": "string",
"error": "string" // Present only if status is "failed"
}
DELETE /videos/:id
Delete a specific video.
DELETE /videos/:id
Delete a specific video.Response: HTTP 204 No Content
HTTP 204 No Content
HTTP 204 No Content
PATCH /videos/:id
Update video name.
PATCH /videos/:id
Update video name.Request Body:
{
"name": "string"
}
Response:
{
"id": "string",
"name": "string",
"status": "string",
"updated_at": "string"
}
Conversations
GET /conversations
List all conversations.
GET /conversations
List all conversations.Query Parameters:
Page number
Page
(optional)
Items per page
Limit
(optional)
Filter by status (“active”|“ended”)
Status
(optional)
{
"conversations": [
{
"id": "string",
"replica_id": "string",
"status": "active|ended",
"created_at": "string",
"ended_at": "string" // Present if status is “ended”
}
],
"pagination": {
"total": 0,
"page": 0,
"pages": 0
}
}
POST /conversations
Create a new conversation.
POST /conversations
Create a new conversation.Request Body:
{
"replica_id": "string",
"name": "string" // Optional
}
Response:
{
"id": "string",
"replica_id": "string",
"status": "active",
"created_at": "string"
}
GET /conversations/:id
Retrieve details of a specific conversation.
GET /conversations/:id
Retrieve details of a specific conversation.Response:
{
"id": "string",
"replica_id": "string",
"status": "active|ended",
"created_at": "string",
"ended_at": "string" // Only if ended
}
POST /conversations/:id
End an active conversation.
POST /conversations/:id
End an active conversation.Response:
{
"id": "string",
"status": "ended",
"ended_at": "string"
}
DELETE /conversations/:id
Delete a conversation.
DELETE /conversations/:id
Delete a conversation.Response: HTTP 204 No Content
HTTP 204 No Content
HTTP 204 No Content
Personas
GET /personas
List all personas.
GET /personas
List all personas.Query Parameters:
Page number
Page
(optional)
Items per page
Limit
(optional)
Response:
{
"personas": [
{
"id": "string",
"name": "string",
"description": "string",
"created_at": "string"
}
],
"pagination": {
"total": 0,
"page": 0,
"pages": 0
}
}
POST /personas
Create a new persona.
POST /personas
Create a new persona.Request Body:
{
"name": "string",
"description": "string",
"voice_settings": {
"pitch": number,
"speed": number
}
}
Response:
{
"id": "string",
"name": "string",
"description": "string",
"created_at": "string"
}
GET /personas/:id
Get details of a specific persona.
GET /personas/:id
Get details of a specific persona.Response:
{
"id": "string",
"name": "string",
"description": "string",
"voice_settings": {
"pitch": number,
"speed": number
},
"created_at": "string"
}
PATCH /personas/:id
Update a persona.
PATCH /personas/:id
Update a persona.Request Body:
{
"name": "string",
"description": "string",
"voice_settings": {
"pitch": number,
"speed": number
}
}
Response:
{
"id": "string",
"name": "string",
"description": "string",
"voice_settings": {
"pitch": number,
"speed": number
},
"updated_at": "string"
}
DELETE /personas/:id
Delete a persona.
DELETE /personas/:id
Delete a persona.Response: HTTP 204 No Content
HTTP 204 No Content
HTTP 204 No Content
Speech
GET /speech
List all speech generations.
GET /speech
List all speech generations.Query Parameters:
Page number
Page
(optional)
Items per page
Limit
(optional)
Filter by status
Status
(optional)
Response:
{
"speech": [
{
"id": "string",
"text": "string",
"status": "processing|ready|failed",
"url": "string",
"created_at": "string"
}
],
"pagination": {
"total": 0,
"page": 0,
"pages": 0
}
}
POST /speech
Generate new speech.
POST /speech
Generate new speech.Request Body:
{
"text": "string",
"persona_id": "string",
"name": "string" // Optional
}
Response:
{
"id": "string",
"status": "processing",
"created_at": "string"
}
GET /speech/:id
Get details of a specific speech generation.
GET /speech/:id
Get details of a specific speech generation.Response:
{
"id": "string",
"text": "string",
"status": "processing|ready|failed",
"url": "string",
"created_at": "string",
"error": "string" // Only present if status is “failed”
}
DELETE /speech/:id
Delete a speech generation.
DELETE /speech/:id
Delete a speech generation.Response: HTTP 204 No Content
HTTP 204 No Content
HTTP 204 No Content
PATCH /speech/:id
Update speech name.
PATCH /speech/:id
Update speech name.Request Body:
{
"name": "string"
}
Response:
{
"id": "string",
"name": "string",
"status": "string",
"updated_at": "string"
}
Error Handling
All API endpoints return appropriate HTTP
status codes:
HTTP
status codes:Headers
Success
200
Resource created
201
Success with no content
204
Bad request - invalid parameters
400
Unauthorized - invalid or missing API key
401
Resource not found
404
Rate limit exceeded
429
Internal server error
500
Bad gateway - proxy error
502
Error responses include a message field with details:
Error Response:
{
"error": "string",
"message": "string",
"status": number
}
Rate Limiting
Keep-alive connections limited to 1 socket per client
Request timeout: 60 seconds
Rate limits are applied per API key
Exceeded limits return 429 status code
Code Examples
JavaScript (Node.js)
// JavaScript
// GET /replicas
const getReplicas = async () => {
const response = await fetch(‘https://api.twintone.ai/v1/replicas’,
{ headers: {‘x-api-key’: ‘your-api-key’ } }); return response.json(); };
// POST /videos
const createVideo = async () => { const response = await
fetch(‘https://api.twintone.ai/v1/video’, { method: ‘POST’, headers: {
‘x-api-key’: ‘your-api-key’, ‘Content-Type’: ‘application/json’ }, body:
JSON.stringify({ replica_id: ‘rep_123’, script: ‘Hello world!’, name: ‘My First
Video’ }) }); return response.json(); };
// PATCH /personas/:id
const updatePersona = async (personaId) => { const
response = await fetch(`https://api.twintone.ai/v1/personas/${personaId}`, {
method: ‘PATCH’, headers: { ‘x-api-key’: ‘your-api-key’, ‘Content-Type’:
‘application/json’ }, body: JSON.stringify({ name: ‘Updated Name’, description:
‘New description’ }) }); return response.json(); };
// DELETE /speech/:id
const deleteSpeech = async (speechId) => { await
fetch(`https://api.twintone.ai/v1/speech/${speechId}`, { method: ‘DELETE’,
headers: { ‘x-api-key’: ‘your-api-key’ } }); };
Python
# Python
import requests
API_KEY = 'your-api-key' BASE_URL = 'https://api.twintone.ai/v1' HEADERS =
{'x-api-key': API_KEY}
# GET /replicas
def get_replicas(): response = requests.get( f'{BASE_URL}/replicas',
headers=HEADERS ) return response.json()
# POST /videos
def create_video(): data = { 'replica_id': 'rep_123', 'script': 'Hello world!',
'name': 'My First Video' } response = requests.post( f'{BASE_URL}/videos',
headers=HEADERS, json=data ) return response.json()
# PATCH /personas/:id
def update_persona(persona_id): data = { 'name': 'Updated Name', 'description':
'New description' } response = requests.patch(
f'{BASE_URL}/personas/{persona_id}', headers=HEADERS, json=data ) return
response.json()
# DELETE /speech/:id
def delete_speech(speech_id): response = requests.delete(
f'{BASE_URL}/speech/{speech_id}', headers=HEADERS ) return response.status_code
GO
// Go
package main
import ( “bytes” “encoding/json” “fmt” “net/http” )
const ( apiKey = “your-api-key” baseURL = “https://api.twintone.ai/vi” )
// GET /replicas func getReplicas() (map[string]interface{}, error) { req, _ :=
http.NewRequest(“GET”, baseURL+“/replicas”, nil) req.Header.Add(“x-api-key”,
apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
func createVideo() (map[string]interface{}, error) { data :=
map[string]string{ “replica_id”: “rep_123”, “script”: “Hello world!”, “name”:
“My First Video”, } jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST", baseURL+"/videos", bytes.NewBuffer(jsonData))
req.Header.Add("x-api-key", apiKey)
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result, nil
}
C#
using System; using System.Net.Http; using System.Text;
using System.Text.Json; using System.Threading.Tasks;
class TwintoneClient { private readonly HttpClient _client; private const string
BaseUrl = "https://api.twintone.ai/vi";public TwintoneClient(string apiKey)
{
_client = new HttpClient();
_client.DefaultRequestHeaders.Add("x-api-key", apiKey);
}
// GET /replicas
public async Task<string> GetReplicas()
{
var response = await _client.GetAsync($"{BaseUrl}/replicas");
return await response.Content.ReadAsStringAsync();
}
// POST /videos
public async Task<string> CreateVideo()
{
var data = new
{
replica_id = "rep_123",
script = "Hello world!",
name = "My First Video"
};
var content = new StringContent(
JsonSerializer.Serialize(data),
Encoding.UTF8,
"application/json"
);
var response = await _client.PostAsync($"{BaseUrl}/videos", content);
return await response.Content.ReadAsStringAsync();
}
// PATCH /personas/:id
public async Task<string> UpdatePersona(string personaId)
{
var data = new
{
name = "Updated Name",
description = "New description"
};
var content = new StringContent(
JsonSerializer.Serialize(data),
Encoding.UTF8,
"application/json"
);
var response = await _client.PatchAsync(
$"{BaseUrl}/personas/{personaId}",
content
);
return await response.Content.ReadAsStringAsync();
}
// DELETE /speech/:id
public async Task DeleteSpeech(string speechId)
{
await _client.DeleteAsync($"{BaseUrl}/speech/{speechId}");
}
Contact
Should you have any further questions, suggestions or feedback regarding this documentation, please contact: James@twintone.ai
Disclaimer
The TwinTone API is provided "as is" without warranty of any kind. TwinTone disclaims all warranties, whether express, implied, or statutory, including without limitation any implied warranties of merchantability, fitness for a particular purpose, or non-infringement. TwinTone does not guarantee that the API will be available uninterrupted or error-free, and TwinTone will not be responsible for any damages or losses arising from the use of the API.
This documentation is subject to change. Please refer to the online version for the most up-to-date information.