Studio API
ZeroCall Studio API를 사용하여 문자 메시지 발송, 수신 관리, 통화 기록 조회, 그룹 관리 등의 기능을 외부 시스템과 연동할 수 있습니다.
Base URL
https://studio-gateway.zerocall.krRate Limit
1,200 requests / 60초| 항목 | 값 |
|---|---|
| 인증 방식 | API Key (x-api-key 헤더) |
| Content-Type | application/json |
| 문자 인코딩 | UTF-8 |
인증
모든 API 요청에는 x-api-key 헤더가 필요합니다. API Key는 ZeroCall 콘솔에서 발급받을 수 있습니다.
API Key 형식
x-api-key: zpa.{apiKeyId}.{secret}API Key 구성요소
- • zpa. - 고정 접두사
- • apiKeyId - API Key 식별자 (숫자)
- • secret - 32바이트 시크릿 키
인증 확인
API Key가 유효한지 확인합니다.
/auth-check// Response
{
"apiKeyId": 123,
"userId": 456,
"name": "My API Key",
"groupIdList": [1, 2, 3]
}그룹 관리
그룹은 전화번호와 연결된 서비스 단위입니다. 그룹을 생성하면 전화번호가 자동 발급됩니다.
그룹 목록 조회
API Key로 접근 가능한 그룹 목록을 조회합니다.
/group// Response
[
{
"id": 1,
"title": "매장A",
"phoneNumber": "07012345678",
"channelId": 100,
"status": "ACTIVE",
"createdAt": "2024-01-15T09:00:00.000Z"
}
]그룹 생성
새 그룹을 생성하고 전화번호를 발급받습니다.
/groupRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| phoneNumber | string | 선택 | 특정 번호 지정 (미입력 시 랜덤 발급) |
| groupTitle | string | 선택 | 그룹 이름 (미입력 시 자동 생성) |
// Request
{
"groupTitle": "신규 매장",
"phoneNumber": "07098765432"
}
// Response
{
"groupId": 123
}그룹 삭제
그룹을 삭제하고 전화번호를 반납합니다.
/group/{groupId}| 파라미터 | 타입 | 설명 |
|---|---|---|
| groupId | number | 삭제할 그룹 ID |
성공 시 204 No Content 응답
전화번호 풀
발급 가능한 전화번호를 확인합니다.
/phone-number-pool/available// Response
{
"isAvailable": true,
"phoneNumber": "07012345678"
}| 필드 | 타입 | 설명 |
|---|---|---|
| isAvailable | boolean | 발급 가능 여부 |
| phoneNumber | string | 샘플 번호 (1개만 반환) |
메시지 발송
SMS/LMS 메시지를 발송합니다. 메시지 길이와 제목 유무에 따라 타입이 자동 결정됩니다.
/message/sendRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| toPhoneNumber | string | 필수 | 수신자 전화번호 |
| content | string | 필수 | 메시지 내용 (최대 2,000자) |
| fromPhoneNumber | string | 선택 | 발신 번호 (미입력 시 기본 번호) |
| subject | string | 선택 | LMS 제목 (최대 64자) |
// Request
{
"toPhoneNumber": "01098765432",
"content": "안녕하세요. 예약이 확정되었습니다.",
"fromPhoneNumber": "07012345678",
"subject": "예약 확정 안내"
}
// Response
{
"chatId": 12345
}메시지 타입 자동 결정
- • SMS - 90바이트 이하
- • LMS - 90바이트 초과 또는 제목 포함
chatId는 웹훅의 SEND_RESULT 이벤트에서 발송 결과를 식별하는 데 사용됩니다.
통화 기록
그룹 단위 통화 기록 목록과 단건 상세 정보를 조회할 수 있습니다.
통화 기록 목록 조회
오프셋 기반 페이지네이션으로 통화 기록을 조회합니다.
/call-historyQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | number | 필수 | 그룹 ID |
| channelId | number | 선택 | 채널 ID (미입력 시 그룹 전체) |
| offset | number | 선택 | 시작 인덱스 (기본값: 0) |
| limit | number | 선택 | 조회 개수 (기본값: 20, 최대: 100) |
GET /call-history?groupId=1&offset=0&limit=20// Response
{
"data": [
{
"callHistoryId": "8f3f8fd9-7ab8-4a26-b523-4f8eb7d38b85",
"fromNumber": "01012345678",
"toNumber": "07012345678",
"isInbound": true,
"status": "ANSWERED",
"tag": "AI_TO_DIRECT",
"duration": 96,
"summary": "예약 변경 요청 후 담당자 연결하여 상담 완료",
"sttAI": [
{ "role": "agent", "text": "안녕하세요. 제로콜입니다. 무엇을 도와드릴까요?", "timestamp": 1.2 },
{ "role": "customer", "text": "내일 예약 시간을 변경하고 싶은데요.", "timestamp": 4.3 },
{ "role": "agent", "text": "네, 예약 변경 도와드리겠습니다. 원하시는 시간대가 있으신가요?", "timestamp": 7.1 },
{ "role": "customer", "text": "오후 3시로 바꿀 수 있을까요? 그리고 담당자랑 직접 통화하고 싶어요.", "timestamp": 11.5 },
{ "role": "agent", "text": "네, 담당자에게 연결해드리겠습니다. 잠시만 기다려주세요.", "timestamp": 16.2 }
],
"sttDirect": [
{ "role": "agent", "text": "안녕하세요, 담당자 김민수입니다.", "timestamp": 1.2 },
{ "role": "customer", "text": "네, 내일 오후 3시로 예약 변경하고 싶어서요.", "timestamp": 4.5 },
{ "role": "agent", "text": "확인해보겠습니다. 오후 3시 자리 있네요. 변경 도와드릴게요.", "timestamp": 9.0 },
{ "role": "customer", "text": "감사합니다.", "timestamp": 14.3 }
],
"recordingAI": {
"url": "https://storage.example.com/recording-ai.wav",
"expiresAt": "2026-03-12T03:40:00.000Z",
"contentLength": 1234567,
"contentType": "audio/wav"
},
"recordingDirect": {
"url": "https://storage.example.com/recording-direct.wav",
"expiresAt": "2026-03-12T03:40:00.000Z",
"contentLength": 2345678,
"contentType": "audio/wav"
},
"channelId": 100,
"groupId": 1,
"createdAt": "2026-03-12T02:40:00.000Z"
}
],
"total": 231,
"offset": 0,
"limit": 20
}통화 기록 상세 조회
특정 통화 기록 ID 기준으로 상세 정보를 조회합니다.
/call-history/{callHistoryId}Path Parameters
| 파라미터 | 타입 | 설명 |
|---|---|---|
| callHistoryId | string | 통화 기록 ID |
Query Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | number | 필수 | 그룹 ID |
GET /call-history/{callHistoryId}?groupId=1status / tag 값
| 필드 | 가능 값 |
|---|---|
| status | ANSWERED`, `MISSED`, `REJECTED`, `UNKNOWN |
| tag | AI`, `DIRECT`, `AI_TO_DIRECT`, `UNKNOWN |
녹음 파일 (recordingAI / recordingDirect)
응답 완료된 통화(status: ANSWERED)의 경우, 통화 유형에 따라 녹음 파일 정보가 포함됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
| url | string | S3 Presigned URL (재생/다운로드용) |
| expiresAt | string | URL 만료 시각 (ISO 8601, 1시간) |
| contentLength | number | 파일 크기 (bytes) |
| contentType | string | Content-Type (audio/wav) |
녹음 파일 URL은 1시간 후 만료됩니다. 파일을 보관해야 하는 경우, URL 수신 후 다운로드하여 별도 저장소에 저장하시기 바랍니다.
웹훅
이벤트 발생 시 등록된 URL로 HTTP POST 요청을 전송합니다.
| 이벤트 | 설명 |
|---|---|
MESSAGE_RECEIVED | 고객으로부터 메시지 수신 |
SEND_RESULT | 메시지 발송 결과 수신 |
CALL_COMPLETED | 통화 종료 후 분석 결과 수신 |
MESSAGE_RECEIVED
고객으로부터 메시지를 수신했을 때 전송됩니다.
{
"eventType": "MESSAGE_RECEIVED",
"timestamp": "2024-03-05T10:30:00.000Z",
"data": {
"chatId": "abc123",
"chatRoomId": "room456",
"groupId": 1,
"channelId": 100,
"phoneNumber": "07012345678",
"senderPhoneNumber": "01098765432",
"content": "안녕하세요, 예약 문의드립니다.",
"subject": null,
"imageList": [],
"receivedAt": "2024-03-05T10:30:00.000Z"
}
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| data.chatId | string | 메시지 고유 ID |
| data.chatRoomId | string | 채팅방 ID |
| data.groupId | number | 그룹 ID |
| data.phoneNumber | string | 수신 번호 (ZeroCall 번호) |
| data.senderPhoneNumber | string | 발신자 번호 (고객) |
| data.content | string | 메시지 내용 |
| data.imageList | string[] | 이미지 URL (MMS) |
이미지 URL 만료 안내
imageList에 포함된 이미지 URL은 일정 시간이 지나면 만료됩니다. 이미지를 보관해야 하는 경우, 웹훅 수신 즉시 다운로드하여 별도 저장소에 저장하시기 바랍니다.
SEND_RESULT
메시지 발송이 완료되었을 때 전송됩니다.
{
"eventType": "SEND_RESULT",
"timestamp": "2024-03-05T10:30:05.000Z",
"data": {
"chatId": "abc123",
"chatRoomId": "room456",
"groupId": 1,
"channelId": 100,
"sendPhoneNumber": "07012345678",
"recvPhoneNumber": "01098765432",
"content": "예약이 확정되었습니다.",
"chatType": "SMS",
"sendStatus": "SUCCESS",
"resultStatus": "00",
"resultMessage": "성공",
"sentAt": "2024-03-05T10:30:05.000Z"
}
}sendStatus 값
| 값 | 설명 |
|---|---|
SUCCESS | 발송 성공 |
FAILED | 발송 실패 |
chatType 값
| 값 | 설명 |
|---|---|
SMS | 단문 메시지 |
LMS | 장문 메시지 |
MMS | 멀티미디어 메시지 |
RCS | RCS 메시지 |
KAKAO | 카카오톡 메시지 |
CALL_COMPLETED
통화 종료 후 STT/요약 분석이 완료되면 전송됩니다.
{
"eventType": "CALL_COMPLETED",
"timestamp": "2026-03-12T02:45:30.000Z",
"data": {
"callHistoryId": "8f3f8fd9-7ab8-4a26-b523-4f8eb7d38b85",
"groupId": 1,
"channelId": 100,
"fromNumber": "01012345678",
"toNumber": "07012345678",
"isInbound": true,
"status": "ANSWERED",
"tag": "AI_TO_DIRECT",
"duration": 96,
"summary": "예약 변경 요청을 접수하고 변경 가능한 시간대를 안내함",
"sttAI": [
{ "role": "agent", "text": "안녕하세요. 제로콜입니다.", "timestamp": 1.2 }
],
"sttDirect": [
{ "role": "customer", "text": "직원 연결 부탁드립니다.", "timestamp": 30.1 }
],
"recordingUrl": "https://storage.example.com/recording.wav",
"startedAt": "2026-03-12T02:40:00.000Z",
"completedAt": "2026-03-12T02:41:36.000Z"
}
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| data.callHistoryId | string | 통화 기록 ID |
| data.groupId | number | 그룹 ID |
| data.channelId | number | 채널 ID |
| data.fromNumber | string | 발신 전화번호 |
| data.toNumber | string | 수신 전화번호 |
| data.isInbound | boolean | 수신 통화 여부 |
| data.status | string | 통화 상태 (`ANSWERED`/`MISSED`/`REJECTED`/`UNKNOWN`) |
| data.tag | string | 통화 태그 (`AI`/`DIRECT`/`AI_TO_DIRECT`/`UNKNOWN`) |
| data.duration | number | 통화 시간(초) |
| data.summary | string | 통화 요약 |
| data.sttAI | object[] | AI 구간 STT (timestamp: 초 단위, 소수점 포함) |
| data.sttDirect | object[] | Direct 구간 STT (timestamp: 초 단위, 소수점 포함) |
| data.recordingUrl | string | 녹음 파일 URL (선택) |
| data.startedAt | string | 통화 시작 시각 (ISO 8601) |
| data.completedAt | string | 통화 완료 시각 (ISO 8601) |
웹훅 처리 권장사항
- • Timeout: 10초
- • HTTP 2xx 응답 시 성공 처리
- • 비동기 처리 후 즉시 응답 권장
에러 처리
API 요청 실패 시 아래 형식으로 에러가 반환됩니다.
{
"status": 401,
"code": "AU002",
"message": "올바르지 않은 토큰입니다."
}HTTP 상태 코드
| 코드 | 설명 |
|---|---|
| 400 | 잘못된 요청 (파라미터 오류) |
| 401 | 인증 실패 (API Key 없음/무효) |
| 403 | 권한 없음 (그룹 접근 불가) |
| 404 | 리소스 없음 |
| 429 | Rate Limit 초과 |
| 500 | 서버 오류 |
코드 예시
cURL
# 그룹 목록 조회
curl -X GET "https://studio-gateway.zerocall.kr/group" \
-H "x-api-key: zpa.123.your-secret-key"
# 메시지 발송
curl -X POST "https://studio-gateway.zerocall.kr/message/send" \
-H "x-api-key: zpa.123.your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"toPhoneNumber": "01098765432",
"content": "안녕하세요. 예약이 확정되었습니다."
}'
# 통화 기록 조회
curl -X GET "https://studio-gateway.zerocall.kr/call-history?groupId=1&offset=0&limit=20" \
-H "x-api-key: zpa.123.your-secret-key"JavaScript
const axios = require('axios');
const client = axios.create({
baseURL: 'https://studio-gateway.zerocall.kr',
headers: {
'x-api-key': 'zpa.123.your-secret-key',
'Content-Type': 'application/json'
}
});
// 메시지 발송
async function sendMessage(to, content) {
const response = await client.post('/message/send', {
toPhoneNumber: to,
content: content
});
return response.data.chatId;
}
// 그룹 목록 조회
async function getGroups() {
const response = await client.get('/group');
return response.data;
}
// 통화 기록 조회
async function getCallHistories(groupId) {
const response = await client.get('/call-history', {
params: { groupId, offset: 0, limit: 20 }
});
return response.data;
}Python
import requests
BASE_URL = 'https://studio-gateway.zerocall.kr'
API_KEY = 'zpa.123.your-secret-key'
headers = {
'x-api-key': API_KEY,
'Content-Type': 'application/json'
}
# 메시지 발송
def send_message(to: str, content: str) -> int:
response = requests.post(
f'{BASE_URL}/message/send',
headers=headers,
json={
'toPhoneNumber': to,
'content': content
}
)
return response.json()['chatId']
# 그룹 목록 조회
def get_groups() -> list:
response = requests.get(f'{BASE_URL}/group', headers=headers)
return response.json()
# 통화 기록 조회
def get_call_histories(group_id: int) -> dict:
response = requests.get(
f'{BASE_URL}/call-history',
headers=headers,
params={'groupId': group_id, 'offset': 0, 'limit': 20}
)
return response.json()웹훅 수신 (Node.js)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const { eventType, data } = req.body;
switch (eventType) {
case 'MESSAGE_RECEIVED':
// 메시지 수신 처리
console.log('메시지 수신:', data.content);
handleIncomingMessage(data);
break;
case 'SEND_RESULT':
// 발송 결과 처리
console.log('발송 결과:', data.sendStatus);
handleSendResult(data);
break;
case 'CALL_COMPLETED':
// 통화 종료 처리
console.log('통화 완료:', data.callHistoryId, data.status, data.tag);
handleCallCompleted(data);
break;
}
// 즉시 200 응답 (비동기 처리 권장)
res.status(200).send('OK');
});
app.listen(3000);