ZeroCall
v1.1콘솔
개발자 문서Studio API

Studio API

ZeroCall Studio API를 사용하여 문자 메시지 발송, 수신 관리, 통화 기록 조회, 그룹 관리 등의 기능을 외부 시스템과 연동할 수 있습니다.

Base URL

https://studio-gateway.zerocall.kr

Rate Limit

1,200 requests / 60초
항목
인증 방식API Key (x-api-key 헤더)
Content-Typeapplication/json
문자 인코딩UTF-8

인증

모든 API 요청에는 x-api-key 헤더가 필요합니다. API Key는 ZeroCall 콘솔에서 발급받을 수 있습니다.

API Key 형식

http
x-api-key: zpa.{apiKeyId}.{secret}

API Key 구성요소

  • zpa. - 고정 접두사
  • apiKeyId - API Key 식별자 (숫자)
  • secret - 32바이트 시크릿 키

인증 확인

API Key가 유효한지 확인합니다.

GET/auth-check
json
// Response
{
  "apiKeyId": 123,
  "userId": 456,
  "name": "My API Key",
  "groupIdList": [1, 2, 3]
}

그룹 관리

그룹은 전화번호와 연결된 서비스 단위입니다. 그룹을 생성하면 전화번호가 자동 발급됩니다.

그룹 목록 조회

API Key로 접근 가능한 그룹 목록을 조회합니다.

GET/group
json
// Response
[
  {
    "id": 1,
    "title": "매장A",
    "phoneNumber": "07012345678",
    "channelId": 100,
    "status": "ACTIVE",
    "createdAt": "2024-01-15T09:00:00.000Z"
  }
]

그룹 생성

새 그룹을 생성하고 전화번호를 발급받습니다.

POST/group

Request Body

필드타입필수설명
phoneNumberstring선택특정 번호 지정 (미입력 시 랜덤 발급)
groupTitlestring선택그룹 이름 (미입력 시 자동 생성)
json
// Request
{
  "groupTitle": "신규 매장",
  "phoneNumber": "07098765432"
}

// Response
{
  "groupId": 123
}

그룹 삭제

그룹을 삭제하고 전화번호를 반납합니다.

DELETE/group/{groupId}
파라미터타입설명
groupIdnumber삭제할 그룹 ID

성공 시 204 No Content 응답

전화번호 풀

발급 가능한 전화번호를 확인합니다.

GET/phone-number-pool/available
json
// Response
{
  "isAvailable": true,
  "phoneNumber": "07012345678"
}
필드타입설명
isAvailableboolean발급 가능 여부
phoneNumberstring샘플 번호 (1개만 반환)

메시지 발송

SMS/LMS 메시지를 발송합니다. 메시지 길이와 제목 유무에 따라 타입이 자동 결정됩니다.

POST/message/send

Request Body

필드타입필수설명
toPhoneNumberstring필수수신자 전화번호
contentstring필수메시지 내용 (최대 2,000자)
fromPhoneNumberstring선택발신 번호 (미입력 시 기본 번호)
subjectstring선택LMS 제목 (최대 64자)
json
// Request
{
  "toPhoneNumber": "01098765432",
  "content": "안녕하세요. 예약이 확정되었습니다.",
  "fromPhoneNumber": "07012345678",
  "subject": "예약 확정 안내"
}

// Response
{
  "chatId": 12345
}

메시지 타입 자동 결정

  • SMS - 90바이트 이하
  • LMS - 90바이트 초과 또는 제목 포함

chatId는 웹훅의 SEND_RESULT 이벤트에서 발송 결과를 식별하는 데 사용됩니다.

통화 기록

그룹 단위 통화 기록 목록과 단건 상세 정보를 조회할 수 있습니다.

통화 기록 목록 조회

오프셋 기반 페이지네이션으로 통화 기록을 조회합니다.

GET/call-history

Query Parameters

파라미터타입필수설명
groupIdnumber필수그룹 ID
channelIdnumber선택채널 ID (미입력 시 그룹 전체)
offsetnumber선택시작 인덱스 (기본값: 0)
limitnumber선택조회 개수 (기본값: 20, 최대: 100)
http
GET /call-history?groupId=1&offset=0&limit=20
json
// 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 기준으로 상세 정보를 조회합니다.

GET/call-history/{callHistoryId}

Path Parameters

파라미터타입설명
callHistoryIdstring통화 기록 ID

Query Parameters

파라미터타입필수설명
groupIdnumber필수그룹 ID
http
GET /call-history/{callHistoryId}?groupId=1

status / tag 값

필드가능 값
statusANSWERED`, `MISSED`, `REJECTED`, `UNKNOWN
tagAI`, `DIRECT`, `AI_TO_DIRECT`, `UNKNOWN

녹음 파일 (recordingAI / recordingDirect)

응답 완료된 통화(status: ANSWERED)의 경우, 통화 유형에 따라 녹음 파일 정보가 포함됩니다.

필드타입설명
urlstringS3 Presigned URL (재생/다운로드용)
expiresAtstringURL 만료 시각 (ISO 8601, 1시간)
contentLengthnumber파일 크기 (bytes)
contentTypestringContent-Type (audio/wav)

녹음 파일 URL은 1시간 후 만료됩니다. 파일을 보관해야 하는 경우, URL 수신 후 다운로드하여 별도 저장소에 저장하시기 바랍니다.

웹훅

이벤트 발생 시 등록된 URL로 HTTP POST 요청을 전송합니다.

이벤트설명
MESSAGE_RECEIVED고객으로부터 메시지 수신
SEND_RESULT메시지 발송 결과 수신
CALL_COMPLETED통화 종료 후 분석 결과 수신

MESSAGE_RECEIVED

고객으로부터 메시지를 수신했을 때 전송됩니다.

json
{
  "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.chatIdstring메시지 고유 ID
data.chatRoomIdstring채팅방 ID
data.groupIdnumber그룹 ID
data.phoneNumberstring수신 번호 (ZeroCall 번호)
data.senderPhoneNumberstring발신자 번호 (고객)
data.contentstring메시지 내용
data.imageListstring[]이미지 URL (MMS)

이미지 URL 만료 안내

imageList에 포함된 이미지 URL은 일정 시간이 지나면 만료됩니다. 이미지를 보관해야 하는 경우, 웹훅 수신 즉시 다운로드하여 별도 저장소에 저장하시기 바랍니다.

SEND_RESULT

메시지 발송이 완료되었을 때 전송됩니다.

json
{
  "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멀티미디어 메시지
RCSRCS 메시지
KAKAO카카오톡 메시지

CALL_COMPLETED

통화 종료 후 STT/요약 분석이 완료되면 전송됩니다.

json
{
  "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.callHistoryIdstring통화 기록 ID
data.groupIdnumber그룹 ID
data.channelIdnumber채널 ID
data.fromNumberstring발신 전화번호
data.toNumberstring수신 전화번호
data.isInboundboolean수신 통화 여부
data.statusstring통화 상태 (`ANSWERED`/`MISSED`/`REJECTED`/`UNKNOWN`)
data.tagstring통화 태그 (`AI`/`DIRECT`/`AI_TO_DIRECT`/`UNKNOWN`)
data.durationnumber통화 시간(초)
data.summarystring통화 요약
data.sttAIobject[]AI 구간 STT (timestamp: 초 단위, 소수점 포함)
data.sttDirectobject[]Direct 구간 STT (timestamp: 초 단위, 소수점 포함)
data.recordingUrlstring녹음 파일 URL (선택)
data.startedAtstring통화 시작 시각 (ISO 8601)
data.completedAtstring통화 완료 시각 (ISO 8601)

웹훅 처리 권장사항

  • • Timeout: 10초
  • • HTTP 2xx 응답 시 성공 처리
  • • 비동기 처리 후 즉시 응답 권장

에러 처리

API 요청 실패 시 아래 형식으로 에러가 반환됩니다.

json
{
  "status": 401,
  "code": "AU002",
  "message": "올바르지 않은 토큰입니다."
}

HTTP 상태 코드

코드설명
400잘못된 요청 (파라미터 오류)
401인증 실패 (API Key 없음/무효)
403권한 없음 (그룹 접근 불가)
404리소스 없음
429Rate Limit 초과
500서버 오류

코드 예시

cURL

bash
# 그룹 목록 조회
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

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

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)

javascript
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);