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 |
공통 성공 응답
성공 응답은 기본적으로 code,message,result 구조를 사용합니다. 아래 API 예시에서는 가독성을 위해 result 값만 표시할 수 있습니다.
{
"code": "0",
"message": "Success",
"result": {
"example": "value"
}
}핵심 개념
ZeroCall의 주요 리소스와 관계를 이해하면 API 활용이 쉬워집니다.
사용자 (User)
ZeroCall에 가입한 계정입니다. 한 사용자는 여러 그룹을 생성하거나 소속될 수 있으며, API Key는 사용자 단위로 발급됩니다.
그룹 (Group)
서비스 운영 단위입니다. 매장, 지점, 프로젝트 등을 그룹으로 구분합니다. 그룹 생성 시 전화번호(채널)가 자동 발급되며, 요금제와 사용량은 그룹 단위로 관리됩니다.
그룹 멤버 (Group Member)
그룹에 소속된 사용자입니다. 멤버는 역할(관리자, 일반)에 따라 권한이 다르며, AI→직원 연결 시 통화를 수신할 수 있습니다.
채널 (Channel)
그룹에 연결된 전화번호입니다. 070 번호가 기본 발급되며, 이 번호로 전화와 문자를 송수신합니다. 한 그룹에 여러 채널을 추가할 수 있습니다.
채팅방 (Chat Room)
채널과 고객 전화번호 조합으로 생성되는 대화 스레드입니다. 동일한 채널-고객 조합은 항상 같은 채팅방을 사용하며, 대화 이력이 누적됩니다.
채팅 (Chat)
채팅방 내의 개별 메시지입니다. SMS/LMS/MMS 타입이 자동 결정되며, 발송 상태(PENDING → SENT/FAILED)와 결과 코드를 통해 전달 여부를 확인할 수 있습니다.
인증
모든 API 요청에는 x-api-key 헤더가 필요합니다. API Key는 ZeroCall 콘솔에서 발급받을 수 있습니다.
API Key 사용 예시
콘솔에서 발급받은 API Key 전체 문자열을 x-api-key 헤더에 그대로 넣어 사용하세요.
x-api-key: {API_KEY}인증 확인
API Key가 유효한지 확인합니다.
/auth-check{
"apiKeyId": 123,
"userId": 456,
"name": "My API Key",
"groupIdList": [1, 2, 3]
}그룹 관리
그룹은 전화번호와 연결된 서비스 단위입니다. 그룹을 생성하면 전화번호가 자동 발급됩니다.
그룹 목록 조회
API Key로 접근 가능한 그룹 목록을 조회합니다.
/group[
{
"id": 1,
"title": "매장A",
"phoneNumber": "07012345678",
"channelId": 100,
"status": "ACTIVE",
"recordingEnabled": true,
"createdAt": "2026-01-15T09:00:00.000Z"
}
]Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| id | number | 그룹 ID |
| title | string | 그룹 이름 |
| phoneNumber | string | 그룹 전화번호 |
| channelId | number | 채널 ID |
| status | string | 상태 (ACTIVE, INACTIVE, PENDING) |
| recordingEnabled | boolean | 통화 녹음 활성화 여부 |
| createdAt | string | 생성일시 (ISO 8601) |
그룹 생성
새 그룹을 생성하고 전화번호를 발급받습니다.
/groupRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupTitle | string | 선택 | 그룹 이름 (미입력 시 자동 생성, 최대 100자) |
| phoneNumber | string | 선택 | 특정 번호 지정 (미입력 시 랜덤 발급) |
| plan | string | 선택 | 요금제 (ECHO, BASIC, BUSINESS, PRO), 기본값: BASIC |
{
"groupTitle": "신규 매장",
"phoneNumber": "07098765432",
"plan": "BASIC"
}
123그룹 삭제
그룹을 삭제하고 전화번호를 반납합니다.
/group/{groupId}| 파라미터 | 타입 | 설명 |
|---|---|---|
| groupId | number | 삭제할 그룹 ID |
성공 시 204 No Content 응답
전화번호 풀
발급 가능한 전화번호를 확인합니다.
/phone-number-pool/available{
"isAvailable": true,
"phoneNumber": "07012345678"
}| 필드 | 타입 | 설명 |
|---|---|---|
| isAvailable | boolean | 발급 가능 여부 |
| phoneNumber | string | 샘플 번호 (1개만 반환) |
채팅방
채팅방을 관리합니다. 채팅방은 고객과의 대화 스레드를 나타냅니다.
채팅방 목록 조회
커서 기반 페이지네이션으로 채팅방 목록을 조회합니다.
/chat-roomQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | number | 필수 | 그룹 ID |
| phoneNumber | string | 선택 | 고객 전화번호 검색 |
| keyword | string | 선택 | 채팅 메시지 내용 검색 |
| cursor | string | 선택 | 페이지네이션 커서 (Base64). 응답의 nextCursor 값을 그대로 사용 |
| size | number | 선택 | 조회 개수 (기본 50, 최대 200) |
{
"data": [
{
"id": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"groupId": 1,
"channelId": 100,
"channelType": "PHONE",
"channelPhoneNumber": "07012345678",
"guestPhoneNumber": "01098765432",
"contactId": 123,
"contactTitle": "홍길동",
"lastMessageContent": "안녕하세요",
"lastMessageAt": "2026-03-05T10:30:00.000Z",
"sequence": 15,
"operatorReadSequence": 15,
"guestReadSequence": 14,
"isChannelRemoved": false,
"matchedChatList": [
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"content": "검색된 메시지 내용",
"timestamp": "2026-03-05T10:25:00.000Z",
"cursor": "eyJjcmVhdGVkQX..."
}
]
}
],
"nextCursor": "eyJsYXN0TWVzc...",
"hasNext": true
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| id | string | 채팅방 ID |
| groupId | number | 그룹 ID |
| channelId | number | 채널 ID |
| channelType | string | 채널 타입 (PHONE) |
| channelPhoneNumber | string | 채널 전화번호 |
| guestPhoneNumber | string | 고객 전화번호 |
| contactId | number | 연락처 ID (등록된 경우) |
| contactTitle | string | 연락처 이름 |
| lastMessageContent | string | 마지막 메시지 내용 |
| lastMessageAt | string | 마지막 메시지 시각 (ISO 8601) |
| sequence | number | 채팅방 내 메시지 시퀀스 |
| operatorReadSequence | number | 운영자가 읽은 시퀀스 |
| guestReadSequence | number | 고객이 읽은 시퀀스 |
| isChannelRemoved | boolean | 채널 삭제 여부 (삭제 시 발송 불가) |
| matchedChatList | array | keyword 검색 시 매칭된 채팅 목록 (keyword 파라미터 사용 시에만 포함) |
| matchedChatList[].id | string | 매칭된 채팅 ID |
| matchedChatList[].content | string | 매칭된 채팅 내용 |
| matchedChatList[].timestamp | string | 매칭된 채팅 시각 (ISO 8601) |
| matchedChatList[].cursor | string | 해당 채팅 위치로 이동하기 위한 커서. GET /chat의 cursor 파라미터에 사용 |
채팅방 상세 조회
특정 채팅방의 상세 정보를 조회합니다.
/chat-room/{chatRoomId}Path Parameters
| 파라미터 | 타입 | 설명 |
|---|---|---|
| chatRoomId | string | 채팅방 ID |
채팅방 생성
새 채팅방을 생성하거나, 동일 채널+전화번호 조합의 기존 채팅방 ID를 반환합니다.
/chat-roomRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| channelId | number | 필수 | 채널 ID |
| phoneNumber | string | 필수 | 고객 전화번호 (숫자만, 0으로 시작) |
{
"channelId": 100,
"phoneNumber": "01098765432"
}
"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"채팅방 나가기
채팅방에서 나갑니다. 채팅방이 삭제되지는 않습니다.
/chat-room/{chatRoomId}성공 시 200 OK 응답
채팅
채팅방 내 대화를 조회하고 메시지를 발송합니다.
대화 조회
채팅방의 대화 내역을 커서 기반 페이지네이션으로 조회합니다.
/chatQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| chatRoomId | string | 필수 | 채팅방 ID |
| cursor | string | 선택 | 페이지네이션 커서 (Base64). 응답의 nextCursor 또는 prevCursor 값을 그대로 사용 |
| cursorDate | string | 선택 | 특정 날짜 이전 메시지 조회 (ISO 8601) |
| size | number | 선택 | 조회 개수 (기본 50, 최대 200) |
{
"data": [
{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"channelId": 100,
"groupId": 1,
"sequence": 15,
"content": "안녕하세요, 예약 문의드립니다.",
"subject": null,
"imageList": [],
"senderType": "GUEST",
"sendStatus": "RECEIVED",
"resultStatus": null,
"chatType": null,
"timestamp": "2026-03-05T10:30:00.000Z"
}
],
"nextCursor": "eyJjcmVhdGVkQX...",
"hasNext": true,
"prevCursor": "eyJjcmVhdGVkQX...",
"hasPrev": true
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| id | string | 메시지 ID |
| chatRoomId | string | 채팅방 ID |
| channelId | number | 채널 ID |
| groupId | number | 그룹 ID |
| sequence | number | 채팅방 내 메시지 순서 |
| content | string | 메시지 내용 |
| subject | string | 제목 (LMS) |
| imageList | string[] | 이미지 URL 목록 |
| senderType | string | 발신자 타입 (GUEST: 고객, OPERATOR: 운영자) |
| sendStatus | string | 발송 상태 (RECEIVED, PENDING, SENT, FAILED) |
| resultStatus | string | 결과 상태 코드 (발송 결과) |
| chatType | string | 메시지 타입 (SMS, LMS, MMS) |
| timestamp | string | 메시지 시각 (ISO 8601) |
양방향 커서 페이지네이션
이 API는 양방향 커서 페이지네이션을 지원합니다. 특정 메시지 위치에서 위/아래로 자유롭게 탐색할 수 있습니다.
- nextCursor: 오래된 메시지 방향으로 이동 (스크롤 위로)
- prevCursor: 최신 메시지 방향으로 이동 (스크롤 아래로)
채팅방 목록에서 검색 후 matchedChatList[].cursor 값을 이 API의 cursor 파라미터에 전달하면 해당 메시지 위치로 바로 이동하여 앞뒤 메시지를 조회할 수 있습니다.
메시지 발송
채팅방에 메시지를 발송합니다.
/chatRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| chatRoomId | string | 필수 | 채팅방 ID |
| content | string | 필수 | 메시지 내용 |
| subject | string | 선택 | LMS 제목 |
| imageList | string[] | 선택 | 이미지 URL 목록 (MMS, 최대 300자/개) |
| externalMessageId | string | 선택 | 외부 메시지 ID (중복 방지용) |
{
"chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"content": "예약 확인되었습니다.",
"subject": null,
"imageList": [],
"externalMessageId": "ext-msg-001"
}
{
"chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"groupId": 1,
"channelId": 100,
"content": "예약 확인되었습니다.",
"subject": null,
"imageList": [],
"externalMessageId": "ext-msg-001",
"sendPhoneNumber": "07012345678",
"recvPhoneNumber": "01098765432",
"sendStatus": "PENDING",
"chatType": "SMS",
"resultStatus": null,
"resultMessage": null,
"createdAt": "2026-03-05T10:31:00.000Z"
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| chatId | string | 메시지 ID |
| chatRoomId | string | 채팅방 ID |
| sendPhoneNumber | string | 발신 번호 |
| recvPhoneNumber | string | 수신 번호 |
| sendStatus | string | 발송 상태 (PENDING → SENT/FAILED) |
| chatType | string | 메시지 타입 (SMS, LMS, MMS) |
| resultStatus | string | 결과 상태 코드 (발송 완료 후) |
| resultMessage | string | 결과 메시지 |
미디어 업로드
채팅에 첨부할 이미지/미디어 파일을 업로드합니다.
/chat/media/uploadRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| files | File[] | 필수 | multipart/form-data 파일 목록 (최대 3개) |
{
"urls": [
"chat-media/123456/1713776400000-abcd123.jpg"
]
}문자 발송
Deprecated이 API는 더 이상 권장되지 않습니다. 새로운 연동에는 채팅 API를 사용해 주세요. 채팅 API는 채팅방 기반으로 대화를 관리하며, 발송 결과 추적이 더 용이합니다.
SMS/LMS/MMS 메시지를 발송합니다. 메시지 길이, 제목, 이미지 첨부 여부에 따라 타입이 자동 결정됩니다.
MMS 이미지 업로드
MMS 발송 전에 이미지 파일을 업로드합니다. 반환된 key를 /message/send의 imageList에 넣어 사용합니다.
/message/media/uploadRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| files | File[] | 필수 | multipart/form-data 파일 목록. 최대 3개 |
응답의 result.urls에는 업로드에 성공한 파일의 key 목록이 들어갑니다. 업로드에 실패한 파일은 이 배열에 포함되지 않습니다.
curl -X POST \
"https://studio-gateway.zerocall.kr/message/media/upload" \
-H "accept: */*" \
-H "x-api-key: {API_KEY}" \
-H "Content-Type: multipart/form-data" \
-F "files=@sample1.png;type=image/png" \
-F "files=@sample2.jpg;type=image/jpg"{
"urls": [
"chat-media/123456/1713776400000-abcd123.jpg",
"chat-media/123456/1713776400001-efgh456.jpg"
]
}이미지 파일 조건
- • 한 번에 최대 3개까지 업로드할 수 있습니다.
- • 이미지 파일은 업로드 후 JPG로 자동 변환되고, 가능하면 MMS 규격에 맞게 축소됩니다.
- • 권장 조건은 최대 1280px, 300KB 이하입니다.
- • 자동 축소 후에도 300KB 이하로 충분히 줄지 않는 이미지가 있을 수 있으니, 가능하면 권장 크기에 맞춰 업로드해 주세요.
- • 일부 이미지는 자동 축소 후에도 MMS 규격을 만족하지 못해 전송이 실패할 수 있습니다.
- • 반환된 key를 그대로 `/message/send`의 `imageList`에 넣으면 MMS로 발송됩니다.
메시지 전송
/message/sendRequest Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| toPhoneNumber | string | 필수 | 수신자 전화번호 (숫자만) |
| content | string | 필수 | 메시지 내용 (최대 2,000자) |
| fromPhoneNumber | string | 선택 | 발신 번호 (숫자만, 내 그룹 번호만 가능) |
| subject | string | 선택 | LMS 제목 (최대 64자) |
| imageList | string[] | 선택 | 이미지/동영상 목록 (S3 key 또는 URL). 값이 있으면 MMS로 발송 |
이 API의 result에는 32자 chatId 문자열이 들어갑니다. 이 값은 이후 SEND_RESULT 웹훅의 data.chatId와 매칭됩니다.
{
"toPhoneNumber": "01098765432",
"content": "안녕하세요. 예약이 확정되었습니다.",
"fromPhoneNumber": "07012345678",
"subject": "예약 확정 안내"
}
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"메시지 타입 자동 결정
- • SMS - 90바이트 이하
- • LMS - 90바이트 초과 또는 제목 포함
- • MMS - imageList에 값이 1개 이상 존재
result에 들어 있는 chatId는 웹훅의 SEND_RESULT 이벤트에서 발송 결과를 식별하는 데 사용됩니다.
메시지 발송 결과 조회
/message/send에서 반환된 chatId로 최종 발송 결과를 조회합니다. 웹훅을 사용하지 않거나, 웹훅 수신 전 상태를 직접 확인해야 할 때 사용할 수 있습니다.
/message/send-result/{chatId}Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| chatId | string | 필수 | /message/send 응답으로 받은 메시지 ID (32자) |
이 API는 SEND_RESULT 웹훅과 동일한 최종 결과 정보를 조회용으로 제공합니다. 성공 여부는 resultStatus로 판단하세요.
curl -X GET \
"https://studio-gateway.zerocall.kr/message/send-result/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "x-api-key: zpa.123.your-secret-key"{
"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"content": "안녕하세요. 예약이 확정되었습니다.",
"subject": "예약 확정 안내",
"imageList": [],
"externalMessageId": "biz_1234567890",
"fromPhoneNumber": "07012345678",
"toPhoneNumber": "01098765432",
"sendStatus": "SUCCESS",
"chatType": "SMS",
"resultStatus": "4100",
"resultMessage": "전달",
"createdAt": "2024-03-05T10:30:05.000Z"
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| id | string | 메시지 ID (32자) |
| content | string | 메시지 내용 |
| subject | string | 제목. 없으면 필드가 생략될 수 있음 |
| imageList | string[] | 이미지 URL 목록 (MMS인 경우) |
| externalMessageId | string | 메시지 사업자 측 외부 메시지 ID |
| fromPhoneNumber | string | 발신 번호 |
| toPhoneNumber | string | 수신 번호 |
| sendStatus | SUCCESS | FAILED | ZeroCall이 메시지 사업자에게 요청을 넘긴 상태 |
| chatType | SMS | LMS | MMS | RCS | KAKAO | 메시지 타입 |
| resultStatus | string | 최종 전달 결과 코드 |
| resultMessage | string | 결과 코드의 한글 메시지 |
| createdAt | string | 메시지 생성 시각 (ISO 8601) |
최종 발송 성공 여부는 resultStatus가 4100,6600,7000 중 하나인지로 판단하세요.
/message/send 요청 실패 시 에러 코드
| HTTP | 코드 | 설명 |
|---|---|---|
| 400 | Validation Error | 요청 형식 오류. 전화번호 형식, 본문 길이, 제목 길이, imageList 항목 길이 등을 확인하세요. |
| 404 | CH001 | 발신에 사용할 채널을 찾을 수 없습니다. |
| 503 | CH002 | 문자 발송에 실패했습니다. |
통화 기록
그룹 단위 통화 기록 목록과 단건 상세 정보를 조회할 수 있습니다.
통화 기록 목록 조회
오프셋 기반 페이지네이션으로 통화 기록을 조회합니다.
/call-historyQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| groupId | number | 필수 | 그룹 ID |
| channelId | number | 선택 | 채널 ID (미입력 시 그룹 전체) |
| phoneNumber | string | 선택 | 전화번호 검색 (발신/수신 번호) |
| keyword | string | 선택 | 통화 요약 내용 검색 |
| offset | number | 선택 | 시작 인덱스 (기본값: 0) |
| limit | number | 선택 | 조회 개수 (기본값: 20, 최대: 100) |
GET /call-history?groupId=1&offset=0&limit=20{
"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"
},
"isRecorded": true,
"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 |
isRecorded 필드
통화 당시 녹음이 활성화되었는지 여부를 나타냅니다. false인 경우 녹음 파일(recordingAI/recordingDirect), 요약(summary), STT 대화 내역(sttAI/sttDirect)이 제공되지 않습니다.
녹음 파일 (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 수신 후 다운로드하여 별도 저장소에 저장하시기 바랍니다.
크레딧/사용량
크레딧 현황 및 사용/충전 이력을 조회합니다.
크레딧 현황 조회
현재 크레딧 잔액과 기본 정보를 조회합니다.
/payment/credit/summary{
"creditBalance": 50000,
"billingStatus": "ACTIVE",
"autoRechargeThreshold": 10000,
"autoRechargeAmount": 50000,
"costMap": {
"sms": 15,
"lms": 40,
"mms": 100,
"call": 2
},
"billingAnchorAt": "2026-01-01T00:00:00.000Z"
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| creditBalance | number | 현재 크레딧 잔액 |
| billingStatus | string | 결제 상태 (ACTIVE, INACTIVE 등) |
| autoRechargeThreshold | number | 자동 충전 기준 금액 |
| autoRechargeAmount | number | 자동 충전 금액 |
| costMap | object | 단가 정보 (sms, lms, mms, call 등) |
| billingAnchorAt | string | 결제 기준일 (ISO 8601) |
사용/충전 이력 조회
크레딧 사용 및 충전 이력을 기간별로 그룹핑하여 조회합니다.
/payment/credit/historyQuery Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
| startDate | string | 선택 | 시작일 (KST, YYYY-MM-DD) |
| endDate | string | 선택 | 종료일 (KST, YYYY-MM-DD) |
| type | string | 선택 | 타입 필터 (AUTO_RECHARGE, PLAN_DEDUCT, MANUAL_ADJUST, AI_CALL_REFUND 등) |
| groupBy | string | 선택 | 그룹핑 단위 (day, week, month), 기본값: day |
| fillEmptyBuckets | boolean | 선택 | 빈 구간도 포함 (기본값: false) |
| includeDetails | boolean | 선택 | 상세 내역 포함 여부 (기본값: false) |
| limit | number | 선택 | 조회 개수 (기본 100, 최대 500) |
| offset | number | 선택 | 오프셋 (기본값: 0) |
{
"effectiveStartDate": "2026-03-01T00:00:00+09:00",
"effectiveEndDate": "2026-03-31T23:59:59+09:00",
"totalCount": 50,
"totalAmount": -15000,
"summary": [
{
"periodStart": "2026-03-05",
"periodEnd": "2026-03-05",
"count": 5,
"totalAmount": -500,
"startBalance": 50000,
"endBalance": 49500,
"amountByType": {
"CALL": -300,
"MESSAGE": -200
},
"amountByUsageCategory": {
"CALL": -300,
"MESSAGE": -200
}
}
],
"details": [
{
"id": 1,
"type": "CALL",
"amount": -100,
"absAmount": 100,
"direction": "DEBIT",
"sourceType": "CALL",
"usageCategory": "CALL",
"balanceBefore": 50000,
"balanceAfter": 49900,
"chatId": null,
"callHistoryId": "8f3f8fd9-7ab8-4a26-b523-4f8eb7d38b85",
"campaignId": null,
"appliedPaymentHistoryId": null,
"description": "통화 사용",
"createdAt": "2026-03-05T10:30:00.000Z"
}
],
"hasMoreDetails": true
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| effectiveStartDate | string | 실제 적용된 조회 시작 시각 (KST) |
| effectiveEndDate | string | 실제 적용된 조회 종료 시각 (KST) |
| totalCount | number | 전체 이력 건수 |
| totalAmount | number | 전체 금액 합계 |
| summary | array | 기간별 요약 (groupBy 기준) |
| details | array | 상세 이력 (includeDetails=true 시) |
| hasMoreDetails | boolean | 추가 상세 데이터 존재 여부 |
usageCategory 값
| 값 | 설명 |
|---|---|
CALL | 일반 통화 사용 |
AI_CALL | AI 통화 사용 |
MESSAGE | 문자 메시지 사용 |
PLAN | 요금제 차감 |
RECHARGE | 크레딧 충전 |
ADJUST | 수동 조정 |
REFUND | 환불 |
그룹별 사용량 조회
그룹별 기본 제공량 대비 사용량을 조회합니다. 현재 결제 기간 기준입니다.
/payment/group/{groupId}/usagePath Parameters
| 파라미터 | 타입 | 설명 |
|---|---|---|
| groupId | number | 그룹 ID |
{
"groupId": 1,
"plan": "BUSINESS",
"startDate": "2026-03-01T00:00:00.000Z",
"endDate": "2026-03-31T23:59:59.999Z",
"initialCallSecond": 3600,
"useCallSecond": 1200,
"remainingCallSecond": 2400,
"initialMessageCount": 100,
"useSmsCount": 15,
"useLmsCount": 5,
"useMmsCount": 5,
"useTotalMessageCount": 25,
"remainingMessageCount": 75,
"initialAiCallCount": 50,
"useAiCallCount": 20,
"remainingAiCallCount": 30
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| groupId | number | 그룹 ID |
| plan | string | 요금제 (ECHO, BASIC, BUSINESS, PRO) |
| startDate | string | 결제 기간 시작일 (ISO 8601) |
| endDate | string | 결제 기간 종료일 (ISO 8601) |
| initialCallSecond | number | 기본 제공 통화 시간 (초) |
| useCallSecond | number | 사용한 통화 시간 (초) |
| remainingCallSecond | number | 남은 통화 시간 (초) |
| initialMessageCount | number | 기본 제공 메시지 건수 |
| useSmsCount | number | 사용한 SMS 건수 |
| useLmsCount | number | 사용한 LMS 건수 |
| useMmsCount | number | 사용한 MMS 건수 |
| useTotalMessageCount | number | 사용한 총 메시지 건수 |
| remainingMessageCount | number | 남은 메시지 건수 |
| initialAiCallCount | number | 기본 제공 AI 통화 건수 |
| useAiCallCount | number | 사용한 AI 통화 건수 |
| remainingAiCallCount | number | 남은 AI 통화 건수 |
웹훅
이벤트 발생 시 등록된 URL로 HTTP POST 요청을 전송합니다.
웹훅 설정 관리
웹훅을 등록, 수정, 삭제하고 테스트할 수 있습니다.
/webhook등록된 웹훅 목록을 조회합니다.
[
{
"id": 1,
"eventType": "MESSAGE_RECEIVED",
"url": "https://example.com/webhook/message",
"createdAt": "2026-01-15T09:00:00.000Z",
"updatedAt": "2026-01-15T09:00:00.000Z"
}
]/webhook웹훅을 등록합니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| eventType | string | 필수 | 이벤트 타입 (MESSAGE_RECEIVED, SEND_RESULT, CALL_RECEIVED, CALL_COMPLETED) |
| url | string | 필수 | 웹훅 수신 URL (https) |
{
"eventType": "MESSAGE_RECEIVED",
"url": "https://example.com/webhook/message"
}
{
"id": 1,
"eventType": "MESSAGE_RECEIVED",
"url": "https://example.com/webhook/message",
"createdAt": "2026-01-15T09:00:00.000Z",
"updatedAt": "2026-01-15T09:00:00.000Z"
}/webhook/{webhookId}웹훅 URL을 수정합니다.
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
| url | string | 필수 | 새로운 웹훅 수신 URL (https) |
/webhook/{webhookId}웹훅을 삭제합니다.
/webhook/{webhookId}/test등록된 웹훅으로 테스트 페이로드를 전송합니다.
{
"success": true,
"statusCode": 200,
"responseTime": 150,
"error": null
}Response 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| success | boolean | 테스트 성공 여부 |
| statusCode | number | HTTP 상태 코드 |
| responseTime | number | 응답 시간 (ms) |
| error | string | 에러 메시지 (실패 시) |
웹훅 이벤트 목록
| 이벤트 | 설명 |
|---|---|
MESSAGE_RECEIVED | 고객으로부터 메시지 수신 |
SEND_RESULT | 메시지 발송 결과 수신 |
CALL_RECEIVED | 전화 수신 시 (착신 알림) |
CALL_COMPLETED | 통화 종료 후 분석 결과 수신 |
MESSAGE_RECEIVED
고객으로부터 메시지를 수신했을 때 전송됩니다.
{
"eventType": "MESSAGE_RECEIVED",
"timestamp": "2024-03-05T10:30:00.000Z",
"data": {
"chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"groupId": 1,
"channelId": 100,
"phoneNumber": "07012345678",
"senderPhoneNumber": "01098765432",
"content": "안녕하세요, 예약 문의드립니다.",
"imageList": [],
"receivedAt": "2024-03-05T10:30:00.000Z"
}
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| data.chatId | string | 메시지 고유 ID (32자) |
| data.chatRoomId | string | 채팅방 ID (30자) |
| data.groupId | number | 그룹 ID |
| data.channelId | number | 채널 ID |
| data.phoneNumber | string | 수신 번호 (ZeroCall 번호) |
| data.senderPhoneNumber | string | 발신자 번호 (고객) |
| data.content | string | 메시지 내용 |
| data.subject | string | 제목. 없으면 필드가 생략될 수 있음 |
| data.imageList | string[] | 이미지 URL (MMS) |
| data.receivedAt | string | 수신 시각 (ISO 8601) |
이미지 URL 만료 안내
imageList에 포함된 이미지 URL은 일정 시간이 지나면 만료됩니다. 이미지를 보관해야 하는 경우, 웹훅 수신 즉시 다운로드하여 별도 저장소에 저장하시기 바랍니다.
SEND_RESULT
메시지 사업자가 최종 발송 결과를 회신했을 때 전송됩니다.
data.chatId는/message/send의result로 받은 32자 문자열과 동일합니다.
아래 값들은 /message/send의result에는 포함되지 않고,SEND_RESULT 웹훅의data 안에서 내려옵니다.
{
"eventType": "SEND_RESULT",
"timestamp": "2024-03-05T10:30:05.000Z",
"data": {
"chatId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"chatRoomId": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"groupId": 1,
"channelId": 100,
"sendPhoneNumber": "07012345678",
"recvPhoneNumber": "01098765432",
"content": "예약이 확정되었습니다.",
"chatType": "SMS",
"sendStatus": "SUCCESS",
"resultStatus": "4100",
"resultMessage": "전달",
"sentAt": "2024-03-05T10:30:05.000Z"
}
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| data.chatId | string | 메시지 고유 ID (32자) |
| data.chatRoomId | string | 채팅방 ID (30자) |
| data.groupId | number | 그룹 ID |
| data.channelId | number | 채널 ID |
| data.sendPhoneNumber | string | 발신 번호 |
| data.recvPhoneNumber | string | 수신 번호 |
| data.content | string | 메시지 내용 |
| data.chatType | SMS | LMS | MMS | RCS | KAKAO | 메시지 타입 |
| data.sendStatus | SUCCESS | FAILED | ZeroCall이 메시지 사업자에게 요청을 넘긴 상태 |
| data.resultStatus | string | 최종 전달 결과 코드 |
| data.resultMessage | string | 결과 코드의 한글 메시지 |
| data.sentAt | string | 메시지 생성 시각 (ISO 8601) |
sendStatus 값
| 값 | 설명 |
|---|---|
SUCCESS | ZeroCall이 메시지 사업자에게 요청을 넘김 |
FAILED | 메시지 사업자 요청 단계에서 실패 |
chatType 값
| 값 | 설명 |
|---|---|
SMS | 단문 메시지 |
LMS | 장문 메시지 |
MMS | 멀티미디어 메시지 |
RCS | RCS 메시지 |
KAKAO | 카카오톡 메시지 |
최종 발송 결과 성공 코드
| 구분 | 성공 코드 | 설명 |
|---|---|---|
| SMS | 4100 | 전달 |
| LMS/MMS | 6600 | 전달 |
| 카카오 | 7000 | 전달 |
정리하면 /message/send는 즉시 code: "0",message: "Success", 그리고 result에 32자chatId를 반환하고, 최종 성공 여부는 나중에 오는 SEND_RESULT 웹훅의 resultStatus가4100,6600,7000 중 하나인지로 판단하세요.
CALL_RECEIVED
전화가 수신되었을 때 전송됩니다. AI 응대 전 또는 직원 연결 전에 착신 알림을 받을 수 있습니다.
{
"eventType": "CALL_RECEIVED",
"timestamp": "2026-03-12T02:40:00.000Z",
"data": {
"callHistoryId": "8f3f8fd9-7ab8-4a26-b523-4f8eb7d38b85",
"groupId": 1,
"groupName": "매장A",
"channelId": 100,
"fromNumber": "01012345678",
"fromName": "홍길동 (매장A)",
"toNumber": "07012345678",
"handOffReason": "고객이 직원 연결을 요청함",
"receivedAt": "2026-03-12T02:40:00.000Z"
}
}필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
| data.callHistoryId | string | 통화 기록 ID |
| data.groupId | number | 그룹 ID |
| data.groupName | string | 그룹 이름 |
| data.channelId | number | 채널 ID |
| data.fromNumber | string | 발신 전화번호 (고객) |
| data.fromName | string | 발신자 이름 (연락처에 등록된 경우) |
| data.toNumber | string | 수신 전화번호 (ZeroCall 번호) |
| data.handOffReason | string | AI→직원 핸드오프 사유 (선택) |
| data.receivedAt | string | 수신 시각 (ISO 8601) |
handOffReason은 AI 응대 중 직원 연결로 전환될 때만 포함됩니다. 일반 착신 시에는 이 필드가 비어있거나 생략될 수 있습니다.
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 | 서버 오류 |
메시지 발송 결과 코드
`resultStatus`는 결과 코드이며, `resultMessage`는 해당 코드의 한글 메시지입니다. 성공 판정은 아래 성공 코드 기준으로 처리하세요.
성공 판정 규칙
| 항목 | 값 |
|---|---|
| successCodes | 4100, 6600, 7000 |
| successMessage | 전달 |
SMS 결과 코드
| resultStatus | resultMessage |
|---|---|
4100 | 전달 |
4400 | 음영 지역 |
4401 | 단말기 전원 꺼짐 |
4402 | 단말기 메시지 저장 초과 |
4403 | 메시지 삭제 됨 |
4404 | 가입자 위치 정보 없음 |
4405 | 단말기 BUSY |
4410 | 잘못된 번호 |
4411 | NPDB 에러 |
4412 | 착신거절 |
4420 | 기타에러 |
4430 | 스팸 |
4431 | 발송 제한 수신거부(스팸) |
LMS/MMS 결과 코드
| resultStatus | resultMessage |
|---|---|
6600 | 전달 |
6601 | 폰 꺼짐 |
6602 | 음영지역 |
6603 | 단말기 문제 |
6604 | 메시지 삭제 됨 |
6605 | 단말기 메시지 저장 개수 초과 |
6610 | 잘못된 번호 |
6611 | 스팸 차단 |
6612 | Spam Content |
6613 | 형식불일치 |
6614 | MMS 미가입자 |
6615 | 기타에러 |
6616 | 통신사 시스템 에러 |
6617 | FDS 차단 |
6618 | 발송불가 시간 |
6619 | MESSAGE RATE EXCEED |
6620 | 서비스 불가 단말 |
6621 | 헤더오류 |
6622 | 잘못된 수신번호 |
6623 | 메시지 길이 오류 |
6624 | 중복메시지 |
6625 | 스팸 차단 |
6626 | 첨부파일 문제 |
6627 | 미지원 단말 |
6628 | 이통사 Timeout |
카카오(알림톡/친구톡) 결과 코드
| resultStatus | resultMessage |
|---|---|
7000 | 전달 |
7001 | 발송 권한 없음 |
7002 | 메시지 형식 오류 |
7003 | 메시지 내용 오류 |
7004 | 메시지 길이 초과 |
7005 | 메시지 버튼 오류 |
7006 | 수신자 번호 오류 |
7007 | 발신프로필키 오류 |
7008 | 발신프로필 접근 권한 없음 |
7009 | 발신프로필 상태 오류 |
7010 | 템플릿 없음 |
7011 | 등록되지 않은 템플릿 |
7012 | 검수 중인 템플릿 |
7013 | 반려된 템플릿 |
7014 | 승인되지 않은 템플릿 |
7015 | 휴면 템플릿 |
7016 | 삭제된 템플릿 |
7017 | 차단된 수신자 |
7018 | 수신자 스팸차단 |
7019 | 수신자 번호 없음 |
7020 | 대체문자 발송 실패 |
7021 | 대체문자 형식 오류 |
7022 | 친구톡 발송 불가 시간 |
7023 | 친구톡 템플릿 없음 |
7999 | 기타 오류 |
코드 예시
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.result; // "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
// 그룹 목록 조회
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) -> str:
response = requests.post(
f'{BASE_URL}/message/send',
headers=headers,
json={
'toPhoneNumber': to,
'content': content
}
)
return response.json()['result'] # "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 그룹 목록 조회
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('chatId:', data.chatId);
console.log('최종 결과:', data.resultStatus, data.resultMessage);
handleSendResult(data);
break;
case 'CALL_RECEIVED':
// 전화 수신 처리
console.log('전화 수신:', data.callHistoryId, data.fromNumber, data.groupName);
handleCallReceived(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);