curl --request POST \
--url https://apiv2.vodex.ai/v1/console/token \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"assistantId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"overrides": {},
"contactRef": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'import requests
url = "https://apiv2.vodex.ai/v1/console/token"
payload = {
"assistantId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"overrides": {},
"contactRef": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
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({
assistantId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
overrides: {},
contactRef: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://apiv2.vodex.ai/v1/console/token', 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://apiv2.vodex.ai/v1/console/token",
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([
'assistantId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'overrides' => [
],
'contactRef' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]),
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://apiv2.vodex.ai/v1/console/token"
payload := strings.NewReader("{\n \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\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://apiv2.vodex.ai/v1/console/token")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apiv2.vodex.ai/v1/console/token")
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 \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n}"
response = http.request(request)
puts response.read_body{
"livekitUrl": "wss://…",
"cells": [
{
"id": "<string>",
"region": "us",
"wsUrl": "<string>"
}
],
"token": "<string>",
"room": "console-1a2b3c4d",
"engine": "cascade"
}{
"error": "not found"
}{
"error": "call minutes exhausted — 30 of 30 minutes used this month",
"code": "quota_exhausted",
"plan": "<string>",
"period": "2026-08",
"usedMinutes": 123,
"limitMinutes": 123
}{
"error": "not found"
}{
"error": "not found"
}Mint a LiveKit token for a web call
The web-call path. The resolved AssistantConfig is embedded in the participant metadata — that is the worker’s only config source for this call, which is what makes transient overrides possible without saving anything.
overrides merges shallowly at the top level, and per-object for the
transcriber, model, voice and startSpeakingPlan slots (FR‑3.3).
Web calls are metered: they are real calls with real provider cost, and exempting them would make the console the way around the limit.
The token is cell-agnostic (shared signing key, config in metadata), so
the client picks the lowest-latency cell from cells and connects
there; livekitUrl is the fallback default. A tenant with region: "in"
is offered only in-region cells, so web-call audio stays in India.
curl --request POST \
--url https://apiv2.vodex.ai/v1/console/token \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"assistantId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"overrides": {},
"contactRef": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
'import requests
url = "https://apiv2.vodex.ai/v1/console/token"
payload = {
"assistantId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"overrides": {},
"contactRef": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
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({
assistantId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
overrides: {},
contactRef: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
})
};
fetch('https://apiv2.vodex.ai/v1/console/token', 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://apiv2.vodex.ai/v1/console/token",
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([
'assistantId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'overrides' => [
],
'contactRef' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]),
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://apiv2.vodex.ai/v1/console/token"
payload := strings.NewReader("{\n \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\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://apiv2.vodex.ai/v1/console/token")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://apiv2.vodex.ai/v1/console/token")
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 \"assistantId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"overrides\": {},\n \"contactRef\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n}"
response = http.request(request)
puts response.read_body{
"livekitUrl": "wss://…",
"cells": [
{
"id": "<string>",
"region": "us",
"wsUrl": "<string>"
}
],
"token": "<string>",
"room": "console-1a2b3c4d",
"engine": "cascade"
}{
"error": "not found"
}{
"error": "call minutes exhausted — 30 of 30 minutes used this month",
"code": "quota_exhausted",
"plan": "<string>",
"period": "2026-08",
"usedMinutes": 123,
"limitMinutes": 123
}{
"error": "not found"
}{
"error": "not found"
}Authorizations
Tenant API key (vdx_live_…) as Authorization: Bearer.
Body
The assistant's uuid, shown on its configure page in the console. Names and slugs are not accepted.
cascade, s2s Transient AssistantConfig overrides for this call only.
An account (contact) id. The agent's {{tokens}} that the
request leaves blank are filled from that contact, as on an
outbound call. The call is not tagged with the contact: it
does not appear on their timeline or in the end-of-call webhook.
Requires contacts:read, runs the saved agent (no overrides
or engine), and always returns a sealed config in the token.