HiAPI
블로그

튜토리얼

hiapi API를 통해 qwen-audio-3.0-tts-plus를 사용하는 방법: curl과 Python으로 텍스트 음성 변환 구현하기

Aug 4, 2026
hiapi API를 통해 qwen-audio-3.0-tts-plus를 사용하는 방법: curl과 Python으로 텍스트 음성 변환 구현하기

qwen-audio-3.0-tts-plus는 hiapi 플랫폼의 통합 비동기 작업 큐를 통해 접근하는 텍스트 음성 변환 모델입니다. 이미지 및 비디오 생성과 동일한 POST /v1/tasks 엔드포인트를 사용하며, 별도의 오디오 API가 아닙니다. 텍스트를 제출하면 즉시 taskId를 받고, 이후 폴링하거나 콜백을 기다려 다운로드 가능한 오디오 파일 URL을 얻습니다. 이 튜토리얼에서는 curl과 Python에서 동작하는 요청(실시간 API에 대해 엔드투엔드 검증 완료)을 완성한 다음, 실제 프로덕션에 필요한 패턴인 콜백과 폴링, 멱등 재시도, 그리고 처리해야 할 정확한 오류 형태를 소개합니다.

구축할 내용

텍스트를 qwen-audio-3.0-tts-plus에 제출하고, 작업 완료를 기다린 뒤 생성된 오디오를 다운로드하는 스크립트입니다. 또한 매개변수 참조와 오류 처리 패턴을 포함하여 이 스크립트를 실제 통합으로 발전시킬 수 있도록 합니다.

사전 조건: hiapi API 키가 필요합니다. hiapi 대시보드에서 발급받으세요. 다음의 모든 요청은 Authorization: Bearer sk-<your-key>로 인증합니다.

최소 동작 예제

curl

작업 생성:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen-audio-3.0-tts-plus",
    "input": {
      "text": "Welcome to hiapi, the unified API for AI models.",
      "voice": "longanlingxin"
    }
  }'
{"code": 200, "data": {"taskId": "tk-hiapi-01KZ59B7ZJ7012PXB5J9GE1QE7"}, "message": "success"}

그런 다음 동일한 bearer 토큰을 사용하여 GET /v1/tasks/<taskId>를 폴링하고, data.status가 "processing"에서 벗어날 때까지 기다립니다. 이 모델의 경우 테스트에서 약 5초가 걸렸습니다:

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01KZ59B7ZJ7012PXB5J9GE1QE7 \
  -H "Authorization: Bearer $HIAPI_KEY"
{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01KZ59B7ZJ7012PXB5J9GE1QE7",
    "status": "success",
    "model": "qwen-audio-3.0-tts-plus",
    "storage": "temp",
    "created": 1785810231,
    "completed": 1785810236,
    "output": [{"type": "audio", "url": "https://temp.hiapi.ai/.../01KZ59B7ZJ7012PXB5J9GE1QE7-0.mp3", "artifactId": "66654", "expireAt": 1786415036}]
  },
  "message": "success"
}

output[0].url은 실제로 다운로드 가능한 MP3입니다(content-type: audio/mpeg — 직접 가져와 확인). 임시 링크이며, storage가 기본값 "temp"로 유지되는 경우 expireAt은 created 이후 거의 정확히 7일 뒤입니다. 따라서 오디오를 장기 보관해야 한다면 바이트를 다운로드하여 직접 저장하세요.

Python

import os
import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
headers = {"Authorization": f"Bearer {os.environ['HIAPI_KEY']}"}

resp = requests.post(
    API_BASE,
    headers={**headers, "Content-Type": "application/json"},
    json={
        "model": "qwen-audio-3.0-tts-plus",
        "input": {
            "text": "Welcome to hiapi, the unified API for AI models.",
            "voice": "longanlingxin",
        },
    },
    timeout=30,
)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]

while True:
    task = requests.get(f"{API_BASE}/{task_id}", headers=headers, timeout=30).json()["data"]
    if task["status"] == "success":
        audio_url = task["output"][0]["url"]
        break
    if task["status"] == "fail":
        raise RuntimeError(task["error"])
    time.sleep(2)

audio_bytes = requests.get(audio_url, timeout=60).content
with open("output.mp3", "wb") as f:
    f.write(audio_bytes)

프로덕션 사용 패턴

전체 입력 스키마

input 객체는 다음 필드만 허용합니다. API는 다른 어떤 필드도 400으로 거부하므로 다른 TTS 제공업체의 매개변수 이름을 그대로 가져오지 마세요:

필드 필수 여부 타입 / 값
text 예 문자열
voice 예 "longanlingxin", "longanlufeng" 중 하나 (이 모델은 현재 두 가지 음색만 사용 가능)
format 아니오 "pcm", "wav", "mp3", "opus" 중 하나 — 생략 시 기본값 mp3
sample_rate 아니오 8000, 16000, 22050, 24000, 44100, 48000 중 하나
pitch 아니오 숫자, 0.5–2
volume 아니오 숫자, 0–100

위의 모든 경계와 열거형 값은 API 자체의 유효성 검사 오류에서 가져온 것입니다. 유효하지 않은 voice 또는 범위를 벗어난 pitch를 전달하면 hiapi가 허용되는 값을 정확히 알려줍니다:

{"code": 400, "data": null, "error_code": "INVALID_REQUEST", "message": "invalid input: voice: value must be one of 'longanlingxin', 'longanlufeng'"}

이 모델에는 emotion, speed 또는 SSML 스타일 제어 필드가 없습니다. 이러한 필드를 포함한 요청은 "additional properties '...' not allowed"로 실패합니다.

콜백 vs. 폴링

간단한 스크립트를 넘어서는 모든 경우에는 폴링을 건너뛰고 hiapi가 결과를 푸시하도록 하세요. 작업 생성 요청에 callback 객체를 추가합니다:

{
  "model": "qwen-audio-3.0-tts-plus",
  "callback": {"url": "https://example.com/hiapi/callback", "when": "final"},
  "input": {"text": "Welcome to hiapi.", "voice": "longanlingxin"}
}

"final"은 현재 when에 대해 지원되는 유일한 값입니다. 작업이 성공하든 실패하든 hiapi는 callback.url로 한 번 POST하며, 해당 페이로드에서 GET /v1/tasks/<taskId> 폴링과 동일한 작업 상세 형태를 읽을 수 있습니다. callback을 완전히 생략하고 위 예제처럼 직접 폴링할 수도 있습니다.

또한 알아두면 좋은 점: 작업 출력은 기본적으로 storage: "temp"입니다(위의 expireAt 값에서 보듯 파일은 약 일주일 동안 유지됨). 오디오를 장기간 검색 가능하게 해야 한다면 작업에 "storage": "persistent"를 설정하세요. 영구 저장은 크기 기준으로 과금되므로 일회성 생성에는 다운로드 후 직접 저장하는 것이 보통 더 저렴합니다.

멱등성과 재시도

POST /v1/tasks는 Idempotency-Key 요청 헤더(최대 255바이트)를 허용합니다. 동일한 계정에서 동일한 키로 동일한 요청을 재시도하면 작업은 한 번만 생성됩니다. 재생 시 두 번째로 과금되는 생성 대신 원래 taskId를 반환합니다. 첫 번째 요청이 도달했는지 확실하지 않은 타임아웃 또는 5xx 이후 재시도할 때 사용하세요:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: tts-job-42" \
  -d '{"model": "qwen-audio-3.0-tts-plus", "input": {"text": "...", "voice": "longanlingxin"}}'

오류 처리

유효하지 않거나 권한이 없는 키는 hiapi의 표준 오류 봉투와 함께 HTTP 401을 반환합니다(이 모델에 대해 직접 검증):

{"error": {"code": "permission_denied", "message": "This API key cannot use the selected model...", "request_id": "...", "type": "hiapi_error"}}

잘못된 input 페이로드는 위 스키마 섹션에서 보듯 위반 필드를 명시한 message와 함께 error_code: "INVALID_REQUEST"를 담은 HTTP 400을 반환합니다. 사람이 읽는 메시지 텍스트가 아니라 error_code / error.code를 기준으로 분기하세요. 작업이 생성 시점에 실패하지 않고 최종 "fail" 상태에 도달하면, 작업 상세 응답은 최상위 error 키 대신 data.error(code + message) 아래에 같은 형태를 담습니다.

관련 문서

자주 묻는 질문

오디오 생성은 hiapi의 이미지/비디오 작업 API와 다른가요? 아니요 — 동일한 POST /v1/tasks 큐, 동일한 생성 후 폴링 또는 콜백 흐름, 동일한 output[0].url 형태입니다. output[0].type(여기서는 "audio")과 input 스키마만 모델에 따라 다릅니다.

format을 설정하지 않으면 어떻게 되나요? MP3를 받게 됩니다 — 테스트에서 생략하여 확인했습니다. wav, pcm 또는 opus가 필요하면 format을 명시적으로 설정하세요.

작업 완료를 기다리지 않고 오디오를 스트리밍할 수 있나요? 아니요. 이 모델은 표준 생성/폴링/콜백 작업 흐름만 제공하며 입력 스키마에 스트리밍 매개변수가 없습니다. 생성 속도도 충분히 빠르므로(짧은 텍스트는 몇 초) 1~2초마다 폴링하면 보통 충분합니다.

출력 URL은 얼마나 유효한가요? 기본적으로 약 7일(storage: "temp")이며, 작업의 created와 expireAt 타임스탬프 간격을 기준으로 합니다. 오디오를 제때 다운로드하거나 더 장기간 검색 가능하게 해야 한다면 작업에 "storage": "persistent"를 설정하세요.

요청이 "additional properties ... not allowed"로 실패하는 이유는 무엇인가요? 스키마가 엄격합니다. 이 모델은 text, voice, format, sample_rate, pitch, volume만 허용합니다. 다른 TTS API의 매개변수(예: emotion 또는 speed)는 여기서 인식되지 않으며 400을 반환합니다.

현재 가격은 어디서 확인할 수 있나요? 가격 페이지에서 확인하세요. 이 튜토리얼은 가격이 변동될 수 있으므로 문자당 또는 요청당 비용을 의도적으로 하드코딩하지 않습니다.

영구 AI 아티팩트로 빌드 시작하기

API 키를 받아 태스크 기반의 하나의 API로 이미지, 비디오, 오디오 모델을 호출하세요.

API 문서 보기