채팅 outbound 메시지 전송
에이전트를 실행하지 않고 발신 채팅 메시지 한 건을 생성한 뒤 지원되는 채널로 전달합니다. origin=human은 Supabase JWT와 X-Vox-Organization-Id 헤더가 필요하며, 호출자가 활성 상담원이자 해당 Chat의 현재 응답자여야 합니다. origin=system은 조직 API key 또는 내부 관리자 인증이 필요합니다.
curl --request POST \
--url https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"origin": "human",
"client_idempotency_key": "<string>",
"text": "<string>",
"images": [
{
"type": "file_key",
"file_key": "file_abc123"
}
],
"navertalk": {
"text_content": {
"text": "<string>",
"quick_reply": {
"button_list": [
{
"type": "TEXT",
"data": {
"title": "<string>",
"code": "<string>"
}
}
]
}
},
"composite_content": null
}
}
'import requests
url = "https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages"
payload = {
"origin": "human",
"client_idempotency_key": "<string>",
"text": "<string>",
"images": [
{
"type": "file_key",
"file_key": "file_abc123"
}
],
"navertalk": {
"text_content": {
"text": "<string>",
"quick_reply": { "button_list": [
{
"type": "TEXT",
"data": {
"title": "<string>",
"code": "<string>"
}
}
] }
},
"composite_content": None
}
}
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({
origin: 'human',
client_idempotency_key: '<string>',
text: '<string>',
images: [{type: 'file_key', file_key: 'file_abc123'}],
navertalk: {
text_content: {
text: '<string>',
quick_reply: {button_list: [{type: 'TEXT', data: {title: '<string>', code: '<string>'}}]}
},
composite_content: null
}
})
};
fetch('https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages', 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/chats/{chat_id}/outbound-messages",
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([
'origin' => 'human',
'client_idempotency_key' => '<string>',
'text' => '<string>',
'images' => [
[
'type' => 'file_key',
'file_key' => 'file_abc123'
]
],
'navertalk' => [
'text_content' => [
'text' => '<string>',
'quick_reply' => [
'button_list' => [
[
'type' => 'TEXT',
'data' => [
'title' => '<string>',
'code' => '<string>'
]
]
]
]
],
'composite_content' => null
]
]),
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/chats/{chat_id}/outbound-messages"
payload := strings.NewReader("{\n \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\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/chats/{chat_id}/outbound-messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages")
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 \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\n }\n}"
response = http.request(request)
puts response.read_body{
"id": 123,
"chat_id": "<string>",
"content": "<string>",
"delivery": {
"channel": "api",
"status": "stored",
"provider_message_ids": [
"<string>"
],
"error_code": "<string>",
"error_message": "<string>"
},
"role": "assistant",
"attachments": [
{
"file_key": "<string>",
"mime_type": "<string>"
}
]
}{
"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": "CHAT_NOT_FOUND",
"message": "채팅을 찾을 수 없습니다.",
"details": {
"chat_id": "22222222-2222-4222-8222-222222222222"
}
}
}{
"error": {
"code": "CONFLICT",
"message": "The requested operation conflicts with current state.",
"details": {
"current_status": "draft"
}
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "요청 본문이 허용 크기를 초과했습니다.",
"details": {
"content_length": 11534336,
"max_bytes": 10485760
}
}
}{
"error": {
"code": "CONVERTED_FILE_TOO_LARGE",
"message": "Converted image size exceeds the 1 MB limit.",
"details": {
"converted_size_bytes": 1258291,
"max_bytes": 1048576
}
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests.",
"details": {
"limit": 10,
"window_seconds": 1,
"scope": "user"
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error.",
"details": {}
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable.",
"details": {}
}
}Authorizations
조직 API 키를 Authorization: Bearer <token> 형식으로 보냅니다.
Headers
1 <= x <= 9223372036854776000Supabase JWT 인증을 사용할 때 필수인 organization UUID입니다. origin=human은 이 인증 경로만 허용합니다.
Path Parameters
Body
이 엔드포인트의 요청 데이터입니다.
메시지를 만든 외부 주체입니다. Public role로 노출하지 않습니다. human은 Supabase JWT와 X-Vox-Organization-Id 헤더가 필요하며, 활성 상담원이 현재 responder인 Chat에서만 사용할 수 있습니다. system은 organization API key 또는 내부 admin만 사용할 수 있습니다.
human, system, tool "human"
클라이언트가 생성한 outbound 메시지 멱등 키입니다. responder-notice namespace는 내부 전용이라 사용할 수 없습니다.
1 - 128assistant-side outbound 메시지 텍스트입니다.
1선택 이미지 입력입니다. 최대 3장까지 지원합니다.
3- ChatMessageFileKeyImageInput
- ChatMessageBase64ImageInput
- ChatMessageUrlImageInput
Show child attributes
Show child attributes
네이버 전용 TEXT/LINK 버튼 또는 최대 10개 카드. text/images와 함께 지정할 수 없습니다.
- NavertalkContentDto
- NavertalkContentDto
Show child attributes
Show child attributes
Response
성공 응답
저장된 assistant 메시지 ID입니다.
채팅 ID입니다.
저장된 assistant 메시지 텍스트입니다.
채널 발송 결과입니다.
Show child attributes
Show child attributes
"assistant"저장된 이미지 첨부 목록입니다.
Show child attributes
Show child attributes
Was this page helpful?
curl --request POST \
--url https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"origin": "human",
"client_idempotency_key": "<string>",
"text": "<string>",
"images": [
{
"type": "file_key",
"file_key": "file_abc123"
}
],
"navertalk": {
"text_content": {
"text": "<string>",
"quick_reply": {
"button_list": [
{
"type": "TEXT",
"data": {
"title": "<string>",
"code": "<string>"
}
}
]
}
},
"composite_content": null
}
}
'import requests
url = "https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages"
payload = {
"origin": "human",
"client_idempotency_key": "<string>",
"text": "<string>",
"images": [
{
"type": "file_key",
"file_key": "file_abc123"
}
],
"navertalk": {
"text_content": {
"text": "<string>",
"quick_reply": { "button_list": [
{
"type": "TEXT",
"data": {
"title": "<string>",
"code": "<string>"
}
}
] }
},
"composite_content": None
}
}
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({
origin: 'human',
client_idempotency_key: '<string>',
text: '<string>',
images: [{type: 'file_key', file_key: 'file_abc123'}],
navertalk: {
text_content: {
text: '<string>',
quick_reply: {button_list: [{type: 'TEXT', data: {title: '<string>', code: '<string>'}}]}
},
composite_content: null
}
})
};
fetch('https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages', 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/chats/{chat_id}/outbound-messages",
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([
'origin' => 'human',
'client_idempotency_key' => '<string>',
'text' => '<string>',
'images' => [
[
'type' => 'file_key',
'file_key' => 'file_abc123'
]
],
'navertalk' => [
'text_content' => [
'text' => '<string>',
'quick_reply' => [
'button_list' => [
[
'type' => 'TEXT',
'data' => [
'title' => '<string>',
'code' => '<string>'
]
]
]
]
],
'composite_content' => null
]
]),
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/chats/{chat_id}/outbound-messages"
payload := strings.NewReader("{\n \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\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/chats/{chat_id}/outbound-messages")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://client-api.tryvox.co/v3/chats/{chat_id}/outbound-messages")
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 \"origin\": \"human\",\n \"client_idempotency_key\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"type\": \"file_key\",\n \"file_key\": \"file_abc123\"\n }\n ],\n \"navertalk\": {\n \"text_content\": {\n \"text\": \"<string>\",\n \"quick_reply\": {\n \"button_list\": [\n {\n \"type\": \"TEXT\",\n \"data\": {\n \"title\": \"<string>\",\n \"code\": \"<string>\"\n }\n }\n ]\n }\n },\n \"composite_content\": null\n }\n}"
response = http.request(request)
puts response.read_body{
"id": 123,
"chat_id": "<string>",
"content": "<string>",
"delivery": {
"channel": "api",
"status": "stored",
"provider_message_ids": [
"<string>"
],
"error_code": "<string>",
"error_message": "<string>"
},
"role": "assistant",
"attachments": [
{
"file_key": "<string>",
"mime_type": "<string>"
}
]
}{
"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": "CHAT_NOT_FOUND",
"message": "채팅을 찾을 수 없습니다.",
"details": {
"chat_id": "22222222-2222-4222-8222-222222222222"
}
}
}{
"error": {
"code": "CONFLICT",
"message": "The requested operation conflicts with current state.",
"details": {
"current_status": "draft"
}
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "요청 본문이 허용 크기를 초과했습니다.",
"details": {
"content_length": 11534336,
"max_bytes": 10485760
}
}
}{
"error": {
"code": "CONVERTED_FILE_TOO_LARGE",
"message": "Converted image size exceeds the 1 MB limit.",
"details": {
"converted_size_bytes": 1258291,
"max_bytes": 1048576
}
}
}{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests.",
"details": {
"limit": 10,
"window_seconds": 1,
"scope": "user"
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error.",
"details": {}
}
}{
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable.",
"details": {}
}
}