플로우 검증
flow({nodes, edges})를 저장하지 않고 검증합니다. valid는 저장 가능 여부를 나타내며, 저장을 막는 치명적 오류가 없으면 true입니다. 런타임 주의 항목은 valid에 영향을 주지 않습니다.
?agent_id를 주면 해당 에이전트의 현재 flow를 기준으로 수정(PATCH) 시 적용되는 참조 검사(orphan·dangling·multifanout)까지 포함합니다. ?level로 응답에 포함할 항목 범주(critical/runtime/all)를 정합니다.
런타임 주의 항목(저장은 가능하지만 실행 중 동작이 달라질 수 있음):
unconnected_skip_user_response_transition: skip/wakeup 전환 행에 나가는 전환이 없습니다.unconnected_fallback_transition: fallback 전환 행에 나가는 전환이 없습니다.no_terminal_reachable: begin 노드에서 도달 가능한 종료 노드(endCall / transferCall / transferAgent)가 없습니다.
curl --request POST \
--url https://client-api.tryvox.co/v3/agents/validate-flow \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"flow": {
"nodes": [
{
"id": "<string>",
"type": "<string>",
"position": {
"x": 123,
"y": 123
}
}
],
"edges": [
{
"source": "<string>",
"target": "<string>",
"condition": {
"type": "<string>",
"prompt": ""
},
"id": "<string>",
"skip_user_response": false
}
]
}
}
'import requests
url = "https://client-api.tryvox.co/v3/agents/validate-flow"
payload = { "flow": {
"nodes": [
{
"id": "<string>",
"type": "<string>",
"position": {
"x": 123,
"y": 123
}
}
],
"edges": [
{
"source": "<string>",
"target": "<string>",
"condition": {
"type": "<string>",
"prompt": ""
},
"id": "<string>",
"skip_user_response": False
}
]
} }
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({
flow: {
nodes: [{id: '<string>', type: '<string>', position: {x: 123, y: 123}}],
edges: [
{
source: '<string>',
target: '<string>',
condition: {type: '<string>', prompt: ''},
id: '<string>',
skip_user_response: false
}
]
}
})
};
fetch('https://client-api.tryvox.co/v3/agents/validate-flow', 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://client-api.tryvox.co/v3/agents/validate-flow",
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([
'flow' => [
'nodes' => [
[
'id' => '<string>',
'type' => '<string>',
'position' => [
'x' => 123,
'y' => 123
]
]
],
'edges' => [
[
'source' => '<string>',
'target' => '<string>',
'condition' => [
'type' => '<string>',
'prompt' => ''
],
'id' => '<string>',
'skip_user_response' => false
]
]
]
]),
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://client-api.tryvox.co/v3/agents/validate-flow"
payload := strings.NewReader("{\n \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\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://client-api.tryvox.co/v3/agents/validate-flow")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://client-api.tryvox.co/v3/agents/validate-flow")
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 \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\n}"
response = http.request(request)
puts response.read_body{
"valid": true,
"errors": [
{
"message": "<string>",
"field": "<string>",
"code": "<string>",
"rule": "<string>",
"node_id": "<string>",
"suggestion": {},
"doc": "<string>",
"details": {}
}
],
"advisories": [
{
"message": "<string>",
"field": "<string>",
"code": "<string>",
"rule": "<string>",
"node_id": "<string>",
"level": "runtime"
}
]
}{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"details": {
"field": "name",
"reason": "must not be blank"
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication is required.",
"details": {}
}
}{
"error": {
"code": "FORBIDDEN",
"message": "Permission denied.",
"details": {}
}
}{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found.",
"details": {
"resource": "agent"
}
}
}{
"error": {
"code": "CONFLICT",
"message": "The requested operation conflicts with current state.",
"details": {
"current_status": "draft"
}
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests.",
"details": {
"limit": 5,
"window_seconds": 1
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error.",
"details": {}
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable.",
"details": {}
}
}Authorizations
조직 API 키를 Authorization: Bearer <token> 형식으로 보냅니다.
Query Parameters
항목 레벨 필터입니다. critical(기본값)은 저장 차단 오류만, runtime은 주의 항목만, all은 둘 다 반환합니다.
critical, runtime, all 수정(PATCH) 검증 대상 에이전트 UUID입니다. 설정하면 해당 에이전트의 현재 flow를 기준으로 참조 검사(orphan·dangling·multifanout)를 함께 수행합니다. 생략하면 생성 기준으로 검증합니다.
Body
검증할 flow 그래프입니다({flow: {nodes, edges}}).
POST /v3/agents/validate-flow 요청 body입니다.
저장하지 않고 검증할 flow graph({nodes, edges})입니다.
Show child attributes
Show child attributes
Response
성공 응답
POST /v3/agents/validate-flow 응답입니다.
flow에 critical blocking issue가 없어 create/update로 안전하게 저장할 수 있으면 true입니다. runtime-level advisory는 이 값에 영향을 주지 않습니다.
저장을 막는 critical issue 목록입니다.
Show child attributes
Show child attributes
?level=runtime 또는 ?level=all 요청에서 반환되는 runtime-level issue입니다. 저장을 막지는 않습니다. 예: 연결되지 않은 skip/fallback wakeup transition, 도달 가능한 terminal node 없음.
Show child attributes
Show child attributes
Was this page helpful?
curl --request POST \
--url https://client-api.tryvox.co/v3/agents/validate-flow \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"flow": {
"nodes": [
{
"id": "<string>",
"type": "<string>",
"position": {
"x": 123,
"y": 123
}
}
],
"edges": [
{
"source": "<string>",
"target": "<string>",
"condition": {
"type": "<string>",
"prompt": ""
},
"id": "<string>",
"skip_user_response": false
}
]
}
}
'import requests
url = "https://client-api.tryvox.co/v3/agents/validate-flow"
payload = { "flow": {
"nodes": [
{
"id": "<string>",
"type": "<string>",
"position": {
"x": 123,
"y": 123
}
}
],
"edges": [
{
"source": "<string>",
"target": "<string>",
"condition": {
"type": "<string>",
"prompt": ""
},
"id": "<string>",
"skip_user_response": False
}
]
} }
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({
flow: {
nodes: [{id: '<string>', type: '<string>', position: {x: 123, y: 123}}],
edges: [
{
source: '<string>',
target: '<string>',
condition: {type: '<string>', prompt: ''},
id: '<string>',
skip_user_response: false
}
]
}
})
};
fetch('https://client-api.tryvox.co/v3/agents/validate-flow', 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://client-api.tryvox.co/v3/agents/validate-flow",
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([
'flow' => [
'nodes' => [
[
'id' => '<string>',
'type' => '<string>',
'position' => [
'x' => 123,
'y' => 123
]
]
],
'edges' => [
[
'source' => '<string>',
'target' => '<string>',
'condition' => [
'type' => '<string>',
'prompt' => ''
],
'id' => '<string>',
'skip_user_response' => false
]
]
]
]),
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://client-api.tryvox.co/v3/agents/validate-flow"
payload := strings.NewReader("{\n \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\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://client-api.tryvox.co/v3/agents/validate-flow")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://client-api.tryvox.co/v3/agents/validate-flow")
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 \"flow\": {\n \"nodes\": [\n {\n \"id\": \"<string>\",\n \"type\": \"<string>\",\n \"position\": {\n \"x\": 123,\n \"y\": 123\n }\n }\n ],\n \"edges\": [\n {\n \"source\": \"<string>\",\n \"target\": \"<string>\",\n \"condition\": {\n \"type\": \"<string>\",\n \"prompt\": \"\"\n },\n \"id\": \"<string>\",\n \"skip_user_response\": false\n }\n ]\n }\n}"
response = http.request(request)
puts response.read_body{
"valid": true,
"errors": [
{
"message": "<string>",
"field": "<string>",
"code": "<string>",
"rule": "<string>",
"node_id": "<string>",
"suggestion": {},
"doc": "<string>",
"details": {}
}
],
"advisories": [
{
"message": "<string>",
"field": "<string>",
"code": "<string>",
"rule": "<string>",
"node_id": "<string>",
"level": "runtime"
}
]
}{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed.",
"details": {
"field": "name",
"reason": "must not be blank"
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication is required.",
"details": {}
}
}{
"error": {
"code": "FORBIDDEN",
"message": "Permission denied.",
"details": {}
}
}{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Resource not found.",
"details": {
"resource": "agent"
}
}
}{
"error": {
"code": "CONFLICT",
"message": "The requested operation conflicts with current state.",
"details": {
"current_status": "draft"
}
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests.",
"details": {
"limit": 5,
"window_seconds": 1
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error.",
"details": {}
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable.",
"details": {}
}
}