HiAPI
블로그

튜토리얼

HiAPI에서 gemini-3.1-flash-tts 호출하는 방법: curl, Python 그리고 실행 가능한 요청

Oct 31, 2026
深靛蓝色背景上多层嵌套的宽箭头依次向右上方推进,箭头前缘为杏橙色,构成规整的信号流示意图形,这是文章的封面图。

gemini-3.1-flash-tts가 HiAPI에서 해주는 일

gemini-3.1-flash-tts는 HiAPI 통합 API로 제공되는 텍스트 음성 변환 모델입니다. 텍스트와 음성 설정을 보내면 플랫폼이 생성된 오디오를 반환합니다. 단일하고 일관된 인터페이스로 노출되므로 모델마다 별도로 통합할 필요가 없습니다. 동일한 인증 헤더, 동일한 기본 URL, 동일한 비동기 작업 패턴이 플랫폼의 모든 TTS 모델에 적용됩니다.

다음과 같은 기능이 필요할 때 이 모델을 선택할 수 있습니다.

  • 다중 화자 내레이션. 스크립트, 대화 또는 팟캐스트 스타일 콘텐츠에서 서로 다른 구간이 서로 다른 목소리처럼 들려야 하는 경우.
  • 빠른 처리. flash 계열은 저지연 생성을 목표로 하며, 이는 대화형 애플리케이션이나 SLA가 빡빡한 배치 작업에서 특히 중요합니다.
  • 예측 가능한 통합 인터페이스. 하나의 요청 형태로 한 줄 텍스트부터 다중 화자 시나리오까지 모두 커버합니다.

이미 다른 HiAPI 모델을 호출하는 애플리케이션을 개발 중이라면, 음성 기능 추가는 모델 식별자를 바꾸고 요청 본문을 조정하는 것만으로 끝납니다. 새로운 SDK도, 새로운 자격 증명 흐름도 필요 없습니다.

사전 조건 및 인증 설정

첫 호출 전에 다음을 갖추었는지 확인하세요.

  1. HiAPI 계정과 API 키. 콘솔에서 키를 생성하세요. 기밀로 취급하고 소스 코드 관리에 커밋하지 마세요.
  2. HiAPI 기본 URL. 모든 요청은 동일한 호스트로 전송되며, 엔드포인트마다 경로만 다릅니다.
  3. HTTP 요청을 보낼 도구. 빠른 테스트용 curl과 스크립트용 Python 3.9+ 및 requests 라이브러리.

인증은 Authorization 헤더에 bearer 토큰을 담는 방식입니다.

Authorization: Bearer $HIAPI_API_KEY

키는 하드코딩하지 말고 환경 변수에 저장하세요.

export HIAPI_API_KEY="your_key_here"

아래에서 설명하는 모든 엔드포인트(작업 제출, 상태 확인, 파일 가져오기)는 동일한 헤더를 사용합니다. 401을 받았다면 헤더가 없거나 형식이 잘못되었거나 키가 폐기된 것입니다. 환경 간에 키를 공유하지 말고 콘솔에서 키를 교체하세요.

다중 화자 음성을 위한 요청 본문 구성

요청 본문은 JSON입니다. 최소한 모델 식별자와 입력 콘텐츠가 포함되어야 합니다. 다중 화자 출력을 위해서는 이름이 지정된 화자를 음성에 매핑하는 화자 설정을 추가하고, 입력 텍스트에 화자 태그를 표시해야 합니다.

일반적인 요청 본문은 다음과 같습니다.

{
  "model": "gemini-3.1-flash-tts",
  "input": {
    "text": "[Host] Welcome back to the show. [Guest] Thanks for having me.",
    "multi_speaker": true,
    "speakers": [
      { "name": "Host", "voice": "voice_a" },
      { "name": "Guest", "voice": "voice_b" }
    ]
  }
}

실수하기 쉬운 핵심 사항:

  • 화자 이름은 정확히 일치해야 합니다. speakers 배열의 name은 텍스트에서 사용한 태그(이 예에서는 대괄호 안)와 정확히 같아야 합니다. 대소문자 불일치는 다중 화자 렌더링 실패의 가장 흔한 원인입니다.
  • 모든 태그를 선언해야 합니다. 텍스트가 [Narrator]를 참조하는데 name: "Narrator"인 화자가 정의되지 않았다면 요청이 거부됩니다.
  • 음성 식별자는 플랫폼의 음성 카탈로그에서 가져옵니다. 임의로 추측하지 말고 HiAPI에서 제공하는 식별자를 사용하세요. 단일 화자 작업의 경우 multi_speaker와 speakers를 생략하고 단일 voice 필드를 설정하세요.
  • 텍스트는 단일 문자열로 전송됩니다. 긴 스크립트도 문제없지만 플랫폼에 문서화된 요청당 제한에 유의하세요.

요청 본문이 검증을 통과하면 작업이 수락되고 작업 식별자가 반환되며, 이를 사용해 폴링합니다.

curl로 실행하기

위의 요청 본문을 payload.json으로 저장한 다음 제출하세요.

curl -X POST "$HIAPI_BASE_URL/v1/audio/speech/jobs" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d @payload.json

응답에는 id와 status 필드가 있는 작업 객체가 포함됩니다(보통 queued 또는 processing).

{ "id": "job_abc123", "status": "queued" }

진행 상황을 확인하려면 ID로 작업을 조회하세요.

curl "$HIAPI_BASE_URL/v1/audio/speech/jobs/job_abc123" \
  -H "Authorization: Bearer $HIAPI_API_KEY"

status가 succeeded가 되면 응답에 오디오 파일 참조(URL 또는 파일 ID)가 포함되며, 후속 요청으로 다운로드할 수 있습니다. 아래 폴링 섹션을 참고하세요. 작업이 실패하면 status는 failed가 되고 이유를 설명하는 error 필드가 함께 제공됩니다.

빠른 실험을 위해서는 파일을 건너뛰고 JSON을 직접 인라인으로 보낼 수 있습니다.

curl -X POST "$HIAPI_BASE_URL/v1/audio/speech/jobs" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gemini-3.1-flash-tts","input":{"text":"Hello from HiAPI.","voice":"voice_a"}}'

Python으로 실행하기

Python 버전은 requests와 폴링 루프의 가독성을 유지하기 위한 작은 헬퍼 함수를 사용합니다. 요청 본문은 curl 예제와 완전히 동일합니다.

import os
import time
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = os.environ["HIAPI_BASE_URL"]
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

payload = {
    "model": "gemini-3.1-flash-tts",
    "input": {
        "text": "[Host] Welcome back to the show. [Guest] Thanks for having me.",
        "multi_speaker": True,
        "speakers": [
            {"name": "Host", "voice": "voice_a"},
            {"name": "Guest", "voice": "voice_b"},
        ],
    },
}

resp = requests.post(
    f"{BASE_URL}/v1/audio/speech/jobs",
    headers={**HEADERS, "Content-Type": "application/json"},
    json=payload,
    timeout=30,
)
resp.raise_for_status()
job = resp.json()
job_id = job["id"]
print("submitted", job_id)

while True:
    r = requests.get(f"{BASE_URL}/v1/audio/speech/jobs/{job_id}", headers=HEADERS, timeout=30)
    r.raise_for_status()
    status = r.json()
    if status["status"] in ("succeeded", "failed"):
        break
    time.sleep(2)

if status["status"] == "failed":
    raise RuntimeError(status.get("error"))

print("audio ready:", status["output"])

주의 사항:

  • 타임아웃이 중요합니다. 모든 요청에 타임아웃을 명시적으로 설정하세요. 멈춘 폴링 루프는 실패한 루프보다 나쁩니다.
  • 너무 자주 폴링하지 마세요. 짧은 스크립트에는 2초 간격이 합리적이며, 긴 스크립트에는 백오프를 적용해야 합니다.
  • 요청 본문을 함수에 넣으세요. 실제 코드에서는 텍스트, 화자, 음성을 매개변수화하여 단일 화자 작업에도 동일한 제출 로직을 재사용할 수 있습니다.

비동기 작업 폴링 및 오디오 다운로드

음성 생성은 비동기입니다. 작업을 제출한 다음 종료 상태에 도달할 때까지 상태를 확인합니다. 종료 상태는 succeeded와 failed 두 가지입니다. 다른 모든 상태는 "계속 대기"로 간주해야 합니다.

견고한 폴링 프로그램은 다음을 수행해야 합니다.

  1. 총 대기 시간을 제한합니다. 빠른 모델이라도 다른 작업 뒤에 대기할 수 있습니다. 합리적인 상한 후에 중단하고 작업 ID를 노출하여 호출자가 재시도할 수 있게 하세요.
  2. 지수 백오프. 1~2초에서 시작해 상한까지 매번 두 배로 늘립니다. 이렇게 하면 긴 작업에서 API에 과도한 부하를 주지 않습니다.
  3. 실패를 명시적으로 처리합니다. status == "failed"일 때 error 필드를 읽고 기록하세요. 이는 보통 요청 본문 문제(선언되지 않은 화자, 빈 텍스트, 지원되지 않는 음성)를 가리킵니다.

작업이 성공하면 상태 응답에 출력 참조가 포함됩니다. 동일한 인증 헤더로 다운로드하세요.

out = status["output"]
audio = requests.get(out["url"], headers=HEADERS, timeout=60)
audio.raise_for_status()
with open("narration.wav", "wb") as f:
    f.write(audio.content)

응답이 URL 대신 파일 ID를 반환하면 HiAPI의 해당 파일 엔드포인트를 통해 가져오세요. 원래 작업 ID를 저장한 파일과 함께 보관하세요. 나중에 잘못된 렌더링을 해당 요청으로 추적할 수 있는 유일하게 신뢰할 수 있는 방법입니다.

문자 단위 과금 이해하기

HiAPI의 gemini-3.1-flash-tts는 입력 텍스트의 문자 수로 과금됩니다. 모델링해야 할 요청당 또는 분당 과금 요소는 없습니다. 작업 비용은 보낸 텍스트 양에 비례하므로 추정이 간단해집니다. 모든 스크립트의 총 문자 수에 현재 문자당 요율을 곱하면 됩니다.

실질적인 영향:

  • 출시 전에 추정하세요. 예상 월간 텍스트 양을 합산하고 가격 패널에 표시된 현재 요율과 대조하세요. 요율을 코드에 하드코딩하지 말고 빌드 시 플랫폼에서 읽거나 설정에 저장하세요.
  • 긴 콘텐츠가 비용을 지배합니다. 10분 분량의 내레이션은 대부분 텍스트 비용입니다. 오디오북이나 강의 자료를 생성한다면 먼저 규모를 모델링하세요.
  • 재시도에도 비용이 발생합니다. 생성 후 실패한 작업도 실패 유형에 따라 과금될 수 있습니다. 제출 전 클라이언트 측 검증을 수행하세요. 모든 화자 태그가 선언되었고 텍스트가 비어 있지 않은지 확인하세요.
  • 적극적으로 캐시하세요. 동일한 스크립트가 반복 렌더링되는 경우(예: 데모나 테스트 픽스처), 동일한 문자에 대해 반복 과금하지 말고 생성된 오디오를 저장하고 재사용하세요.

현재 요율과 규모별 구간에 대해서는 웹사이트의 가격 패널을 확인하세요. 그것이 유일한 사실 출처입니다.

흔한 실패 지점과 해결 방법

대부분의 통합 문제는 몇 가지 범주로 나뉩니다. 순서대로 하나씩 점검하세요.

401 Unauthorized

Authorization 헤더가 없거나 형식이 잘못되었습니다. 키가 환경에 설정되어 있고 헤더 내용이 정확히 Bearer <key>인지 확인하세요. 키가 교체되었다면 환경을 업데이트하세요.

400 Bad Request — 알 수 없는 화자

텍스트에 speakers에 선언되지 않은 [Name] 태그가 있거나 대소문자가 일치하지 않습니다. 태그와 name 필드를 바이트 단위로 완전히 동일하게 만드세요.

400 Bad Request — 입력이 비었거나 너무 큼

빈 텍스트는 즉시 검증에서 실패합니다. 매우 긴 스크립트는 요청당 제한을 초과할 수 있습니다. 자르지 말고 여러 작업으로 분할하세요.

400 Bad Request — 유효하지 않은 음성 식별자

음성 이름은 플랫폼의 카탈로그에서 가져와야 합니다. 식별자를 정확히 복사하세요. 임의의 친숙한 이름을 만들어내지 마세요.

작업이 processing 상태에 멈춤

보통 버그가 아니라 대기 중입니다. 백오프와 총 타임아웃을 적용하세요. 정상 시간을 훨씬 넘겨서도 계속된다면 작업 ID를 기록하고 더 작은 요청 본문으로 한 번 재시도하여 크기가 문제인지 판단하세요.

작업이 생성 오류와 함께 failed

error 필드를 읽으세요. 보통 일시적 장애가 아니라 콘텐츠나 구성 문제를 가리킵니다. 요청 본문을 수정하고 다시 제출하세요. 무작위 재시도 루프는 피하세요. 시도할 때마다 문자에 대해 과금될 수 있습니다.

오디오를 다운로드할 수 있지만 재생되지 않음

파일이 손상되었다고 가정하기 전에 다운로드한 파일의 콘텐츠 유형을 확인하세요. 반환된 형식에 맞는 올바른 확장자로 저장하고 바이트 길이가 0이 아닌지 검증하세요. 파일이 잘린 경우는 보통 다운로드 요청이 중단된 것을 의미합니다. 다시 생성하지 말고 다운로드를 재시도하세요.

이러한 점검을 통해 대부분의 첫날 통합 문제는 몇 시간이 아니라 몇 분 안에 해결할 수 있습니다.

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

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

API 문서 보기