Create asset group (liveness)
curl --request POST \
--url https://api.gravitex.ai/v1/visual-validate/session \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"description": "<string>",
"channel_id": 123
}
'import requests
url = "https://api.gravitex.ai/v1/visual-validate/session"
payload = {
"name": "<string>",
"description": "<string>",
"channel_id": 123
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', description: '<string>', channel_id: 123})
};
fetch('https://api.gravitex.ai/v1/visual-validate/session', 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.gravitex.ai/v1/visual-validate/session",
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([
'name' => '<string>',
'description' => '<string>',
'channel_id' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"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://api.gravitex.ai/v1/visual-validate/session"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
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://api.gravitex.ai/v1/visual-validate/session")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gravitex.ai/v1/visual-validate/session")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}"
response = http.request(request)
puts response.read_bodyPlatform API
Create asset group (liveness)
POST /v1/visual-validate/session (liveness_face)
POST
/
v1
/
visual-validate
/
session
Create asset group (liveness)
curl --request POST \
--url https://api.gravitex.ai/v1/visual-validate/session \
--header 'Authorization: <authorization>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "<string>",
"description": "<string>",
"channel_id": 123
}
'import requests
url = "https://api.gravitex.ai/v1/visual-validate/session"
payload = {
"name": "<string>",
"description": "<string>",
"channel_id": 123
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': 'application/json'},
body: JSON.stringify({name: '<string>', description: '<string>', channel_id: 123})
};
fetch('https://api.gravitex.ai/v1/visual-validate/session', 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.gravitex.ai/v1/visual-validate/session",
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([
'name' => '<string>',
'description' => '<string>',
'channel_id' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"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://api.gravitex.ai/v1/visual-validate/session"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
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://api.gravitex.ai/v1/visual-validate/session")
.header("Authorization", "<authorization>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.gravitex.ai/v1/visual-validate/session")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"channel_id\": 123\n}"
response = http.request(request)
puts response.read_bodyIntroduction
Live-person asset groups (group_type: liveness_face) cannot be created via Create asset group. Call this endpoint first to start H5 liveness validation; the gateway persists the group after the user completes validation.
| Library | group_type | How to create |
|---|---|---|
| Virtual | aigc | POST /v1/asset-groups |
| Live person | liveness_face | H5 validation via this endpoint |
BytePlus
CreateVisualValidateSession does not accept name or description. After receiving upstream GroupId, the gateway calls UpdateAssetGroup to write those fields back to the BytePlus console.Authentication
string
required
Bearer Token, e.g.
Bearer sk-xxxxxxxxxxstring
application/jsonRequest body
string
required
Asset group name (stored and synced to the BytePlus console)
string
Description; defaults to API key user
username if omitted or empty (same as Create asset group)integer
Upstream channel ID; auto-selected if omitted
H5 validation flow
1. Client starts validation โ POST /v1/visual-validate/session
โ returns { h5_link, state, ... }
2. Client opens h5_link in a popup; user completes validation
โ BytePlus redirects to gateway /asset-validate-callback.html?state=โฆ&bytedToken=โฆ&resultCode=10000
3. Callback page fetches /v1/visual-validate/result; persists group_type=liveness_face
โ window.opener.postMessage({ type: 'gravitex-asset-validate-result', ok, group_id, โฆ })
4. Client receives group_id; upload assets via POST /v1/assets (Image / Video / Audio)
POST /v1/visual-validate/result directly; the gateway callback page forwards state automatically.
Example
curl -X POST "https://api.gravitex.ai/v1/visual-validate/session" \
-H "Authorization: Bearer sk-xxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Live person A"}'
Response
{
"h5_link": "https://verify.byteplus.com/h5/?token=โฆ&lang=zh-CN&lng=zh",
"state": "<base64url>.<hmac>",
"channel_id": 123,
"byted_token": "bp-token-xxxxxxxx",
"expires_in": 900
}
| Field | Description |
|---|---|
h5_link | BytePlus H5 liveness URL; gateway appends lang=zh-CN&lng=zh for Simplified Chinese by default |
state | Gateway-issued HMAC-SHA256 token bound to user / channel / group_name / byted_token; used by the callback page to fetch results |
byted_token | BytePlus credential for this validation session; verified against the value inside state on callback |
channel_id | Selected upstream channel |
expires_in | state token TTL in seconds, fixed at 900 (15 minutes). Not the H5 page lifetimeโthe BytePlus H5 link is valid for about 120 seconds; call this endpoint again after expiry |
Clients typically only need to open
h5_link in a popup and listen for window.message. On H5 timeout, close the popup, remove the listener, and call POST /v1/visual-validate/session again for a new link. Consider a client-side timeout of about 130s as a fallback.Client postMessage
The callback page (/asset-validate-callback.html) posts to window.opener (targetOrigin = '*'). If the callback page and parent are on different origins, identify messages by type rather than relying only on event.origin.
Success:
{
"type": "gravitex-asset-validate-result",
"ok": true,
"group_id": "group-20260512083014-zyxwv",
"name": "Live person A",
"channel_id": 123,
"group_type": "liveness_face"
}
{
"type": "gravitex-asset-validate-result",
"ok": false,
"result_code": "10003",
"error": "Liveness validation failed: face does not match reference"
}
const popup = window.open(session.h5_link, 'asset-validate', 'width=480,height=720');
const listener = (event) => {
const data = event.data;
if (!data || data.type !== 'gravitex-asset-validate-result') return;
window.removeEventListener('message', listener);
if (data.ok) {
console.log('Asset group created:', data.group_id);
} else {
console.error('Validation failed:', data.error, data.result_code);
}
};
window.addEventListener('message', listener);
Next steps
- Use
group_idfrom postMessage (not from this endpointโs synchronous response) with Create asset - Poll List assets or Get asset until
status: active - Reference assets with
asset://in Create video generation
?group_type=liveness_face).