Calls API
발신·조회·종료 REST 엔드포인트 레퍼런스.
베이스 URL과 인증
https://vox-gateway-prod.fly.dev모든 요청에 번호 API 키를 Bearer로 실습니다. 키가 곧 발신 번호이자 접근 범위입니다 — 그 번호의 통화만 조회·종료할 수 있습니다.
Authorization: Bearer tg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json오류는 {"error": "코드", ...} 형태의 평평한 봉투입니다. 어떤 코드가 어느 상태로 오는지는 에러 코드 한 곳에 모아 뒀습니다 — 엔드포인트 절에서 다시 나열하지 않습니다.
통화 객체
통화 하나를 나타내는 값입니다. 아래 조회 엔드포인트와 Webhook, 콘솔 통화 이력이 모두 같은 어휘를 씁니다.
| 필드 | 타입 | 설명 |
|---|---|---|
id | string | 통화 id. 발신 응답의 call_id와 같은 값입니다. |
number_id | string | 이 통화를 건·받은 번호의 id. |
direction | string | outbound(발신) 또는 inbound(수신). |
counterparty | string | 상대 번호(국내형) — 수신이면 발신자, 발신이면 착신자. |
status | string | ringing · active · completed · no_answer · failed. 뜻은 핵심 개념 › 통화 수명주기에 있습니다. |
started_at · ended_at | string | ISO 8601. 진행 중이면 ended_at은 null입니다. |
end_reason | string | 끝난 통화가 어떻게 끝났는지 (아래 표). 진행 중이면 null입니다. |
instruction | string | 발신 요청에 실었던 이번 통화의 임무. |
summary | string | 내장 Agent 통화의 자동 요약. 자체 Agent 통화는 null입니다. |
recording | string | 녹음 유무 — available · pending · none · failed. |
error | string | 실패한 통화의 오류 문자열. |
폴링용 GET /api/v1/calls/{call_id}는 상태·시각·실제 연결시간과 런타임 옵션만 돌려줍니다.
end_reason은 오류가 아니라 통화가 어떻게 끝났는지입니다. 상태 조회 · Webhook · 통화 이력이 모두 같은 값을 씁니다.
| end_reason | 콘솔 표시 | 뜻 |
|---|---|---|
user_hangup | 유저 종료 | 상대가 먼저 끊었습니다. |
agent_hangup | AI 종료 | AI가 종료 API를 불러 끊었습니다. |
no_answer | — | 벨이 울렸지만 받지 않았습니다. |
busy | 통화 중 | 상대 회선이 통화 중이었습니다 (SIP 486). |
declined | 수신 거절 | 연결된 뒤 수신이 거절됐습니다. |
not_found | 번호 없음 | 없는 번호입니다 (SIP 404). |
network_error | 네트워크 오류 | 통화 중 네트워크가 끊겼습니다. |
system_error | 시스템 오류 | 내부 오류로 통화가 중단됐습니다. |
system_busy | 시스템 혼잡 | 플랫폼 용량이 차서 통화를 유지하지 못했습니다. |
max_duration_reached | 최대 통화시간 초과 | 최대 통화시간에 도달해 자동 종료됐습니다. |
silence_timeout | 무음 종료 | 유도 후에도 응답이 없어 무음으로 종료됐습니다. |
auth_rejected | 인증 거절 | 수신 통화가 발신자 인증에서 거절됐습니다. |
voicemail | 음성사서함 | 발신이 통신사 음성사서함에 연결돼 자동 종료됐습니다 (연결·과금은 됐습니다). |
unknown | 알 수 없음 | 사유를 판별하지 못했습니다. |
no_answer는 상태 배지가 이미 “부재중”을 보여줘 콘솔에서 사유를 따로 표시하지 않습니다.
POST /api/v1/calls
전화를 겁니다. 응답은 발신 직후에 돌아오고, 통화는 비동기로 진행됩니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
to | string | 예 | 착신번호(국내형). 하이픈·공백 허용. |
instruction | string | 아니오 | 이번 통화의 임무. 내장 Agent는 이대로 대화하고, 자체 Agent에는 kickoff 턴으로 전달됩니다. |
language | string | 아니오 | ko · vi · en · zh · es. 넘기지 않으면 음성 설정에 저장된 통화 언어로 겁니다. |
max_duration_sec | integer | 아니오 | 이 통화만 gateway의 통화 정책 범위 안에서 제한합니다. 번호의 최대 통화 시간보다 길게 설정할 수는 없습니다. |
발신 번호를 넣는 필드는 없습니다 — API 키가 결정합니다.
curl -X POST https://vox-gateway-prod.fly.dev/api/v1/calls \
-H "Authorization: Bearer $VOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "01012345678", "instruction": "예약 확정을 안내한다.", "language": "ko", "max_duration_sec": 60}'201 Created
{ "call_id": "9f1c…", "status": "ringing", "from": "07079193558" }status는 항상 ringing입니다. 상대가 받았는지는 상태 조회나 Webhook으로 확인하세요.
대부분의 거절은 전화를 걸기 전에 나므로 통화 기록도 요금도 생기지 않습니다. 다이얼까지 간 실패에는 end_reason이 함께 옵니다. 거절 사유는 에러 코드에 모아 두었습니다.
GET /api/v1/calls/{call_id}
통화 상태와 현재 적용된 런타임 옵션을 조회합니다. 폴링용으로 가볍습니다.
curl https://vox-gateway-prod.fly.dev/api/v1/calls/{call_id} \
-H "Authorization: Bearer $VOX_API_KEY"200 OK
{
"status": "completed",
"end_reason": "agent_hangup",
"started_at": "2026-08-11T04:20:24.106000+00:00",
"ended_at": "2026-08-11T04:21:07.812000+00:00",
"duration_sec": 31,
"options": { "interruption_enabled": true, "language": "ko" },
"options_pending": false
}duration_sec은 상대가 받은 뒤 연결이 끊길 때까지 워커가 측정한 실제 연결시간입니다. 벨이 울린 시간은 포함하지 않습니다. 진행 중이거나 종료 사용량 보고가 아직 없으면 null이고, 상대가 받지 않은 no_answer는 연결시간이 확정적으로 0입니다.
options는 이 통화에 지금 걸려 있는 값입니다 — 옵션을 바꾼 적이 없으면 통화가 시작할 때 실린 설정값입니다. options_pending: true는 변경 요청이 아직 통화에 닿지 않았다는 뜻입니다.
요약·상대 번호까지 필요하면 아래 /api/my/calls를 쓰세요. 내 번호의 통화가 아니면 404입니다 — 존재 여부를 알려주지 않습니다.
거절 사유는 에러 코드에 모아 두었습니다.
GET /api/v1/calls/{call_id}/transcript
통화 대화 전문을 순서대로 돌려줍니다.
curl https://vox-gateway-prod.fly.dev/api/v1/calls/{call_id}/transcript \
-H "Authorization: Bearer $VOX_API_KEY"200 OK
{
"call_id": "9f1c…",
"transcript": [
{ "sequence": 1, "role": "agent", "text": "안녕하세요, …", "t_offset_sec": 1.2, "timestamp": "…" },
{ "sequence": 2, "role": "user", "text": "네, 말씀하세요", "t_offset_sec": 4.8, "timestamp": "…" }
]
}role은 user(상대) 또는 agent(AI), t_offset_sec은 통화 시작 기준 경과 초입니다. 통화 중에도 호출할 수 있으나 그 시점까지 확정된 발화만 담깁니다 — 빈 배열이 “아직 발화 전”인지 “말 없이 끝난 통화”인지는 통화 상태를 따로 조회해야 갈립니다.
거절 사유는 에러 코드에 모아 두었습니다.
GET /api/v1/calls/{call_id}/recording
녹음 파일을 오디오 바이트로 직접 스트리밍합니다. 시한부 링크를 발급하거나 저장소로 리다이렉트하지 않으므로, 서버 간 연동은 응답 스트림을 그대로 재생·보관하면 됩니다.
curl https://vox-gateway-prod.fly.dev/api/v1/calls/{call_id}/recording \
-H "Authorization: Bearer $VOX_API_KEY" \
-H "Range: bytes=0-" \
--output call.ogg| 응답 | 뜻 |
|---|---|
200 | 전체 오디오 스트림. |
206 | 요청한 Range 구간. Content-Range와 Accept-Ranges 헤더가 함께 옵니다. |
409 | 아직 파일이 없습니다. reason은 pending · none · failed 중 하나입니다. |
Content-Type은 현재 audio/ogg입니다. 과거·향후 포맷은 파일 확장자에 맞춰 audio/mpeg 등이 올 수 있으므로 응답 헤더를 따르세요.
Range를 전달하면 긴 녹음도 처음부터 전부 받지 않고 재생 위치를 옮길 수 있습니다.- API 키가 브라우저에 노출되므로
<audio src>에 이 URL을 직접 넣지 마세요. 브라우저 재생은 여러분 서버가 인증 헤더와 Range를 중계해야 합니다. - 다른 번호의 통화와 없는 통화는 모두 404입니다. 저장소 URL과 object key는 응답에 나오지 않습니다.
녹음 상태가 available인지 먼저 확인하려면 아래 /api/my/calls의 recording 필드를 보세요. 거절 사유는 에러 코드에 모아 두었습니다.
POST /api/v1/calls/{call_id}/hangup
통화 종료를 요청합니다. 즉시 끊지 않고 진행 중인 발화(작별 인사)가 끝나기를 기다린 뒤 끊기 때문에 응답이 200이 아니라 202입니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
call_id | string | 예 | 경로에 넣습니다. 발신 응답(call_id)이나 Webhook의 call.started에서 얻습니다. |
immediate | boolean | 아니오 | true면 발화를 기다리지 않고 곧바로 끊습니다. 기본 false — 통화 중인 agent는 그대로 두세요. |
본문 자체가 선택입니다. 없거나 깨져 있으면 immediate=false로 처리합니다.
curl -X POST https://vox-gateway-prod.fly.dev/api/v1/calls/{call_id}/hangup \
-H "Authorization: Bearer $VOX_API_KEY"202 Accepted
{ "call_id": "9f1c…", "status": "hangup_requested" }- 인사 도중 상대가 다시 말하면 종료가 자동 취소되고 통화가 이어집니다.
- 거절돼도 통화는 살아 있습니다 — 죽이지 말고 사유를 받아 대화를 이어가세요.
- 실제 종료 확정은
call.endedWebhook이나 상태 조회로 관찰합니다. 사유는end_reason: "agent_hangup"(콘솔 표시 “AI 종료”).
거절 사유는 에러 코드에 모아 두었습니다.
PATCH /api/v1/calls/{call_id}/options
진행 중인 그 통화 하나에만 옵션을 껐다 켭니다. 워크스페이스·번호에 저장된 설정은 바뀌지 않습니다 — 통화가 끝나면 저장값이 그대로입니다. 종료와 마찬가지로 요청만 전달하고 실제 적용은 통화를 쥔 워커가 하므로 응답이 202이고, 반영은 다음 발화부터입니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
call_id | string | 예 | 경로에 넣습니다. 발신 응답(call_id)이나 Webhook의 call.started에서 얻습니다. |
interruption_enabled | boolean | 아니오 | false면 AI가 발화를 끝까지 말하고 그동안 상대의 말은 무시합니다(끼어들기 잠금). 본인확인·고지 멘트를 읽는 구간에 씁니다. |
language | string | 아니오 | 통화 언어(ko · vi · en · zh · es). 인식 언어와 목소리가 그 언어로 옮겨가고, 목소리는 고른 화자가 그대로 유지됩니다. 내장 Agent가 말하는 언어는 통화 시작 값 그대로입니다 — 자체 Agent는 매 요청에 오는 통화 컨텍스트의 language로 바뀐 언어를 알 수 있습니다. |
두 옵션은 한 요청에 함께 보낼 수 있습니다. 아는 필드가 하나도 없으면 400 invalid_request_body입니다 — 아무것도 안 하는 202를 돌려주지 않습니다. 모르는 필드는 무시하므로 나중에 늘어날 옵션을 함께 보내도 안전합니다.
curl -X PATCH https://vox-gateway-prod.fly.dev/api/v1/calls/{call_id}/options \
-H "Authorization: Bearer $VOX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"interruption_enabled": false}'202 Accepted
{
"call_id": "9f1c…",
"status": "options_update_requested",
"options": { "interruption_enabled": false }
}- 현재 값은
GET /api/v1/calls/{call_id}의options에서 확인합니다. 요청이 아직 워커에 닿지 않았으면options_pending: true입니다 — 202는 접수이지 적용이 아닙니다. - 언어 전환은 통화가 쓰는 TTS 프로바이더 안에서만 일어납니다. 통화 중에는 프로바이더를 바꿀 수 없어, 그 프로바이더가 못 하는 언어로의 전환은 422
language_unavailable로 거절되고 통화는 그대로 진행됩니다. 바뀐 언어는 인식은 곧바로, 목소리는 다음 발화부터 적용됩니다. - 되돌릴 필요는 없습니다 — 통화가 끝나면 옵션도 함께 사라집니다. 다음 통화는 저장된 설정으로 시작합니다.
거절 사유는 에러 코드에 모아 두었습니다.
GET /api/my/calls
최근 통화를 최신순으로 나열합니다. 상태만 있는 v1 조회와 달리 상대 번호 · 임무 · 요약까지 돌려줍니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
limit | number | 아니오 | 돌려줄 개수(1~200). 기본 20입니다. 쿼리스트링으로 넘깁니다. |
curl "https://vox-gateway-prod.fly.dev/api/my/calls?limit=20" \
-H "Authorization: Bearer $VOX_API_KEY"200 OK
[
{
"id": "9f1c…",
"number_id": "6b2e…",
"direction": "outbound",
"counterparty": "01012345678",
"status": "completed",
"started_at": "2026-08-11T04:20:24.106000+00:00",
"ended_at": "2026-08-11T04:21:07.812000+00:00",
"end_reason": "agent_hangup",
"instruction": "예약 확정을 안내한다.",
"summary": "8월 3일 오후 2시 예약을 안내하고 상대가 확인함.",
"error": null,
"recording": "available"
}
]- 단건 조회는
GET /api/my/calls/{call_id}이고 같은 객체를 돌려줍니다. counterparty는 상대 번호입니다(수신이면 발신자, 발신이면 착신자).summary는 내장 Agent 통화에만 생성됩니다 — 자체 Agent 통화는 전문이 여러분 시스템에 있으므로null입니다.recording은 녹음 유무일 뿐 링크가 아닙니다. 녹음을 실제로 받으려면 위 v1 녹음 스트림을 호출하세요. MCP 커넥터는 오디오를 JSON에 실을 수 없어 별도로 시한부 링크를 발급합니다.
거절 사유는 에러 코드에 모아 두었습니다.