본문으로 건너뛰기

일반 STT

일반 STT API는 음성 파일을 텍스트로 변환하는 HTTP 기반 REST API입니다.

지원 포맷​

팁

일반 STT API는 mp4, m4a, mp3, amr, flac, wav 음성 파일 형식을 지원합니다.

인증 토큰 발급​

일반 STT API는 인증 가이드를 통해 토큰을 발급한 후 사용할 수 있습니다.


API 목록

MethodURLDescription
POST/v1/transcribe파일 전사 요청
GET/v1/transcribe/{TRANSCRIBE_ID}파일 전사 결과 조회

1) [POST] /v1/transcribe​

저장된 음성 파일에 대해 전사를 요청하는 API입니다.

HTTP 요청​

POST https://openapi.vito.ai/v1/transcribe

요청 헤더​

Authorization: Bearer {YOUR_JWT_TOKEN}
  • scheme: bearer
  • bearerFormat: JWT

요청 바디 (Request body)​

content-type: multipart/form-data

FieldTypeRequired
configRequestConfigrequired
fileBinaryrequired

RequestConfig

NameDescTypeRequiredValueDefault
model_name음성 인식 모델stringoptionalsommers, whispersommers
language음성 인식 언어. whisper 모델 사용 시 필수. sommers 지원 언어는 ko, jastringoptionalko, ja, (whisper) detect, multiko
language_candidates언어 감지 후보군. language가 detect 또는 multi일 때만 적용arrayoptional["ko", "ja", "zh", "en"]
use_diarization화자 분리 사용 여부booleanoptionalfalse
diarization.spk_count화자 수. use_diarization이 true일 때만 적용integeroptional0 이상의 정수0 (화자 수 예측)
use_itn영어/숫자/단위 표기 변환 사용 여부booleanoptionaltrue
use_disfluency_filter간투어 필터 사용 여부booleanoptionaltrue
use_profanity_filter비속어 필터 사용 여부booleanoptionalfalse
use_paragraph_splitter문단 나누기 사용 여부booleanoptionaltrue
paragraph_splitter.max문단 최대 글자 수. use_paragraph_splitter이 true일 때만 적용integeroptional1 이상의 정수50
domain음성 파일 유형 (도메인)stringoptionalGENERAL, CALLGENERAL
use_word_timestamp단어 단위 타임스탬프 사용 여부booleanoptionalfalse
keywords키워드 부스팅용 단어 목록arrayoptional
주의

일반 STT API에는 다음과 같은 제약이 있습니다.

  1. POST API 동시처리 제한: 동시처리 가능한 파일 개수는 처리량 제한 정책을 따릅니다. POST API로 요청한 뒤 처리가 완료되기 전까지의 요청 개수를 의미하며, API 처리에 대한 완료는 아래 GET API를 통해 확인 가능합니다.
  2. 최대 인식파일 크기: 2GB, 최대 인식가능 시간: 4시간.
  3. 전사 작업은 요청 순서에 따라 순차적으로 처리됩니다. 최대 인식 가능 시간인 4시간 분량의 음성 파일도 일반적으로 5분 이내에 작업이 시작되지만, 요청이 많은 시간대에는 작업 시작까지 최대 30분 이상 소요될 수 있습니다.

샘플 코드 1​

transcribe.sh
curl -X "POST" \
"https://openapi.vito.ai/v1/transcribe" \
-H "accept: application/json" \
-H "Authorization: Bearer ${YOUR_JWT_TOKEN}" \
-H "Content-Type: multipart/form-data" \
-F "file=@sample.wav" \
-F 'config={}'

샘플 코드 2​

transcribe.sh
curl -X "POST" \
"https://openapi.vito.ai/v1/transcribe" \
-H "accept: application/json" \
-H "Authorization: Bearer ${YOUR_JWT_TOKEN}" \
-H "Content-Type: multipart/form-data" \
-F "file=@sample.wav" \
-F 'config={
"use_diarization": true,
"diarization": {
"spk_count": 2
},
"use_itn": false,
"use_disfluency_filter": false,
"use_profanity_filter": false,
"use_paragraph_splitter": true,
"paragraph_splitter": {
"max": 50
}
}'

응답 바디 (Response Body)​

응답이 성공한 경우 HTTP Status 200과 함께 아래와 같은 응답을 내려줍니다.

{
"id": "{TRANSCRIBE_ID}"
}

오류 코드​

HTTP StatusCodeNotes
400H0001잘못된 파라미터 요청
400H0010지원하지 않는 파일 포맷
401H0002유효하지 않은 토큰
413H0005파일 사이즈 초과
413H0006파일 길이 초과
429A0001사용량 초과
429A0002동시 처리 제한 초과
500E500서버 오류

아래는 실패 응답 예시입니다.

{
"code": "H0001",
"msg": "unexpected end of JSON input"
}

2) [GET] /v1/transcribe/{TRANSCRIBE_ID}​

  • 음성 파일의 전사 결과를 조회하는 API입니다. 전사 요청 API에서 응답받은 TRANSCRIBE_ID를 사용하여 전사 결과를 조회할 수 있습니다.

HTTP 요청​

GET https://openapi.vito.ai/v1/transcribe/{TRANSCRIBE_ID}

요청 헤더​

Authorization: Bearer {YOUR_JWT_TOKEN}
  • scheme: bearer
  • bearerFormat: JWT

샘플 코드​

get_transcript.sh
curl -X "GET" \
"https://openapi.vito.ai/v1/transcribe/${TRANSCRIBE_ID}" \
-H "accept: application/json" \
-H "Authorization: Bearer ${YOUR_JWT_TOKEN}"

응답 바디 (Response Body)​

응답이 성공한 경우 HTTP Status 200과 함께 아래와 같은 응답을 내려줍니다.

NameDescTypeValue
idtranscribe idstring
status전사 결과 상태stringtranscribing, completed, failed
results.utterances발화 정보array
results.utterances.start_at발화 시작 시각 (ms)integer
results.utterances.duration발화 지속 시간 (ms)integer
results.utterances.msg발화 텍스트string
results.utterances.spk화자/채널 IDinteger
results.utterances.langlanguage로 설정한 언어, 또는 detect/multi인 경우에는 모델이 예측한 언어stringISO 639-1 language code
팁

일반 STT API의 경우, 긴 음성 파일도 지원하기 위하여 Polling 방식으로 구현되어 있습니다. 전사 요청 API에서 응답받은 {TRANSCRIBE_ID}의 상태 값이 transcribing인 경우, 최종 상태(completed 또는 failed)가 될 때까지 주기적으로 조회하여 변환 결과를 확인할 수 있습니다. 권장하는 Polling 주기는 5초입니다. (Polling 주기가 너무 짧을 경우 HTTP Status 429로 요청 제한 초과 응답이 내려갈 수 있습니다.)

status: transcribing

{
"id": "{TRANSCRIBE_ID}",
"status": "transcribing"
}

status: completed

{
"id": "{TRANSCRIBE_ID}",
"status": "completed",
"results": {
"utterances": [
{
"start_at": 4737,
"duration": 2360,
"msg": "안녕하세요.",
"spk": 0,
"lang": "ko"
},
{
"start_at": 8197,
"duration": 3280,
"msg": "네, 안녕하세요? 반갑습니다.",
"spk": 1,
"lang": "ko"
}
]
}
}

status: failed

요청은 성공했지만 전사가 실패한 경우 아래와 같은 응답이 반환됩니다.

{
"id": "{TRANSCRIBE_ID}",
"status": "failed",
"error": {
"code": "{ERROR_CODE}",
"message": "{MESSAGE}"
}
}

아래는 예시입니다.

{
"id": "ZbOOQftrS1ywK_T3ikuveA",
"status": "failed",
"error": {
"code": "E500",
"message": "internal server error"
}
}

위와 같은 오류가 지속되면 문의해 주시기 바랍니다.

오류 코드​

HttpStatusCodeNotes
400H0001잘못된 파라미터 요청
401H0002유효하지 않은 토큰
403H0003권한 없음
404H0004전사 결과 없음
410H0007전사 결과 만료됨
429A0003요청 제한 초과
500E500서버 오류

아래는 응답이 실패한 경우 가운데 하나의 예시입니다.

{
"code": "H0004",
"msg": "not found"
}

샘플 코드 (단일 예제 + 프리셋)​

아래 단일 스크립트에서 PRESET 환경 변수로 원하는 설정을 선택할 수 있습니다. 기본값은 sommers_basic입니다.

transcribe.py
import json
import os
import time
from typing import Any, Dict, Optional

import requests


class RTZROpenAPIClient:
"""Minimal client for RTZR OpenAPI (auth + STT file).

- Fetches JWT via /v1/authenticate using client_id/client_secret
- Submits a file transcription job via /v1/transcribe
- Polls /v1/transcribe/{id} every few seconds until completed/failed
"""

def __init__(
self,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
base_url: str = "https://openapi.vito.ai",
) -> None:
self.base_url = base_url.rstrip("/")
self.client_id = client_id or os.getenv("RTZR_CLIENT_ID")
self.client_secret = client_secret or os.getenv("RTZR_CLIENT_SECRET")
if not self.client_id or not self.client_secret:
raise ValueError(
"Missing credentials. Set RTZR_CLIENT_ID and RTZR_CLIENT_SECRET "
"environment variables, or pass client_id/client_secret to RTZROpenAPIClient."
)
self._sess = requests.Session()
self._token: Optional[Dict[str, Any]] = None

@property
def token(self) -> str:
# Renew if missing or expiring within 30 minutes
if self._token is None or self._token.get("expire_at", 0) < time.time() - 1800:
resp = self._sess.post(
f"{self.base_url}/v1/authenticate",
data={"client_id": self.client_id, "client_secret": self.client_secret},
)
resp.raise_for_status()
self._token = resp.json()
access = self._token.get("access_token")
if not access:
raise RuntimeError("authenticate: 'access_token' not found in response")
return access

def _auth_headers(self) -> Dict[str, str]:
return {"Authorization": f"Bearer {self.token}"}

def transcribe_file(self, file_path: str, config: Dict[str, Any]) -> Dict[str, Any]:
url = f"{self.base_url}/v1/transcribe"
with open(file_path, "rb") as f:
files = {"file": (os.path.basename(file_path), f)}
data = {"config": json.dumps(config)}
resp = self._sess.post(url, headers=self._auth_headers(), files=files, data=data)
resp.raise_for_status()
return resp.json()

def get_transcription(self, transcribe_id: str) -> Dict[str, Any]:
url = f"{self.base_url}/v1/transcribe/{transcribe_id}"
resp = self._sess.get(url, headers=self._auth_headers())
resp.raise_for_status()
return resp.json()

def wait_for_result(
self,
transcribe_id: str,
poll_interval_sec: int = 5,
timeout_sec: int = 3600,
) -> Dict[str, Any]:
deadline = time.time() + timeout_sec
while True:
if time.time() > deadline:
raise TimeoutError("Timed out waiting for transcription result")
result = self.get_transcription(transcribe_id)
status = result.get("status")
if status in ("completed", "failed"):
return result
time.sleep(poll_interval_sec)


# Preset configurations
PRESETS: Dict[str, Dict[str, Any]] = {
"sommers_basic": { # 1) sommers without diarization
"model_name": "sommers",
"use_diarization": False,
"domain": "GENERAL",
},
"sommers_call_diarization": { # 2) sommers + diarization + CALL, spk_count=2
"model_name": "sommers",
"domain": "CALL",
"use_diarization": True,
"diarization": {"spk_count": 2},
},
"sommers_ja": { # 3) sommers japanese without diarization
"model_name": "sommers",
"use_diarization": False,
"domain": "GENERAL",
"language": "ja",
},
"whisper_en_diarization": { # 4) whisper + diarization, language=en
"model_name": "whisper",
"language": "en",
"use_diarization": True,
},
# Additional commonly requested options
"paragraph_split_80": {"use_paragraph_splitter": True, "paragraph_splitter": {"max": 80}},
"keywords_example": {"keywords": ["stt", "returnzero", "api"]},
"with_word_timestamps": {"use_word_timestamp": True},
"disfluency_on": {"use_disfluency_filter": True},
"profanity_on": {"use_profanity_filter": True},
"whisper_detect_multi": {
"model_name": "whisper",
"language": "multi",
"language_candidates": ["ko", "en", "ja"],
},
}


def main():
audio_path = os.getenv("AUDIO_PATH", "sample.wav")
preset_name = os.getenv("PRESET", "sommers_basic")

if preset_name not in PRESETS:
raise ValueError(f"Unknown PRESET '{preset_name}'. Available: {sorted(PRESETS.keys())}")

config = PRESETS[preset_name]

client = RTZROpenAPIClient()

submit = client.transcribe_file(audio_path, config)
transcribe_id = submit.get("id")
result = client.wait_for_result(transcribe_id, poll_interval_sec=5)
print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == "__main__":
main()

사용 가능한 프리셋은 다음과 같습니다.

프리셋모델언어도메인화자 분리기타
sommers_basicsommers—GENERALoff—
sommers_call_diarizationsommers—CALLon (diarization.spk_count=2)—
whisper_en_diarizationwhisperen—on—
paragraph_split_80————use_paragraph_splitter=true, paragraph_splitter.max=80
keywords_example————keywords=["stt", "returnzero", "api"]
with_word_timestamps————use_word_timestamp=true
disfluency_on————use_disfluency_filter=true
profanity_on————use_profanity_filter=true
whisper_detect_multiwhispermulti——language_candidates=["ko", "en", "ja"]
전체 설정 JSON 보기 (선택)
{
"sommers_basic": {
"model_name": "sommers",
"use_diarization": false,
"domain": "GENERAL"
},
"sommers_call_diarization": {
"model_name": "sommers",
"domain": "CALL",
"use_diarization": true,
"diarization.spk_count": 2
},
"whisper_en_diarization": {
"model_name": "whisper",
"language": "en",
"use_diarization": true
},
"paragraph_split_80": {
"use_paragraph_splitter": true,
"paragraph_splitter.max": 80
},
"keywords_example": {
"keywords": ["stt", "returnzero", "api"]
},
"with_word_timestamps": {
"use_word_timestamp": true
},
"disfluency_on": {
"use_disfluency_filter": true
},
"profanity_on": {
"use_profanity_filter": true
},
"whisper_detect_multi": {
"model_name": "whisper",
"language": "multi",
"language_candidates": ["ko", "en", "ja"]
}
}