Typecast Gateway API
한 번의 요청으로 대사 하나를 음성으로 만듭니다. 모든 엔드포인트는 /api/v1 아래에 있고 JSON으로 응답합니다.
인증
모든 요청에 관리자 페이지에서 발급한 토큰이 필요합니다.
Authorization: Bearer tcg_xxxxxxxxxxxxxxxxxxxxxxxx
토큰이 없으면 401 TOKEN_REQUIRED, 값이 틀리면 401 INVALID_TOKEN이 돌아옵니다. 토큰 값은 해시로만 저장되므로 분실하면 새로 발급해야 합니다.
/api/v1/speech대사 하나를 음성으로 생성합니다.
curl -X POST https://typecast.jeong.su/api/v1/speech \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "오늘 영상도 끝까지 봐주세요",
"voiceId": "tc_6059dad0b83880769a50502f",
"emotion": "normal",
"speed": 1.3,
"requestId": "project-1:scene-3"
}'| 필드 | 타입 | 설명 |
|---|---|---|
text* | string | 생성할 대사. 앞뒤 공백을 제외하고 1~500자. |
voiceId* | string | Typecast 목소리 ID. /api/v1/voices 에서 조회합니다. |
emotion | string | 감정 프리셋. 기본 normal. 목소리가 지원하지 않는 값은 normal로 대체합니다. |
speed | number | 0.5 ~ 2.0, 기본 1. 소수 첫째 자리로 반올림합니다. |
model | string | null | 엔진 버전(ssfm-v30, ssfm-v21). 생략하면 ssfm-v30으로 생성합니다. |
speaker | string | null | 화자 라벨. TubeLab 경로에만 전달되며 결과 음성에는 영향이 없습니다. |
requestId | string | null | 재시도 식별자. 같은 값으로 다시 호출하면 첫 결과를 그대로 반환해 중복 결제를 막습니다. |
provider | "auto" | "typecast" | "tubelab" | 기본 auto. typecast는 TubeLab을 건너뛰고, tubelab은 TubeLab만 사용합니다. |
200 OK
{
"audioSrc": "/api/v1/audio/2f1c8f0e-...",
"mediaId": "2f1c8f0e-...",
"durationMs": 2480,
"chars": 14,
"provider": "tubelab",
"keyId": null,
"keyLabel": null,
"cached": false,
"maxChars": 500
}| 필드 | 타입 | 설명 |
|---|---|---|
audioSrc | string | 오디오 다운로드 경로. 이 서비스 기준 상대 경로입니다. |
mediaId | string | 오디오 식별자(UUID). |
durationMs | number | 음성 길이(밀리초). |
chars | number | 과금 기준 글자 수. |
provider | "tubelab" | "typecast" | 실제로 생성에 사용한 경로. |
keyId / keyLabel | string | null | Typecast 키로 생성한 경우 그 키. TubeLab이면 null. |
cached | boolean | true면 기존 음성을 재사용했고 크레딧을 쓰지 않았습니다. |
maxChars | number | 한 요청의 글자 수 상한. |
/api/v1/audio/{mediaId}생성된 음성 파일을 내려받습니다. 기본 7일 보관하며, 만료 후에는 410을 반환합니다.
curl https://typecast.jeong.su/api/v1/audio/2f1c8f0e-... \ -H "Authorization: Bearer $TOKEN" -o scene-3.wav 200 OK Content-Type: audio/wav
/api/v1/voices사용 가능한 목소리 목록입니다. 15분간 캐시합니다.
200 OK
{
"voices": [
{
"voiceId": "tc_69fc0cff784968297fb45daa",
"name": "Sanghyun",
"gender": "male",
"age": "young-adult",
"language": "ko",
"style": null,
"emotions": ["normal", "happy", "sad", "angry", "toneup", "tonedown"],
"models": ["ssfm-v30", "ssfm-v21"]
}
],
"keyId": "...",
"keyLabel": "..."
}목소리마다 지원하는 models와 emotions가 다릅니다. 생성 요청의 model·emotion은 이 목록 안의 값이어야 합니다.
/api/v1/status지금 요청이 들어오면 어떤 순서로 시도하는지, 경로별 잔여량을 반환합니다.
200 OK
{
"tubelab": {
"configured": true,
"dailyChars": { "used": 1200, "limit": 3000, "remaining": 1800 },
"dailyProductions": { "used": 1, "limit": 2 }
},
"typecastKeys": [
{
"id": "...",
"label": "우경",
"routeOrder": 1,
"renewalDay": 30,
"daysUntilRenewal": 17,
"proxied": true,
"usage": {
"plan": "free",
"used": 7023,
"limit": 15000,
"remaining": 7977,
"concurrencyLimit": 2
},
"usageError": null
}
]
}TubeLab 세션이 없으면 tubelab은 { configured: false, error } 형태이고, 특정 키의 사용량 조회가 실패하면 usage가 null, usageError에 이유가 담깁니다.
생성 경로와 순서
- 캐시 —
voiceId·text·emotion·speed·model이 같은 음성이 이미 있으면 그대로 반환합니다 (cached: true, 크레딧 소모 없음). 돈을 쓰기 전에 모든 경로의 캐시를 먼저 확인하므로, 예전에 TubeLab으로 만든 음성도 재사용됩니다. - TubeLab — 오늘 남은 문자 수가 충분하면 여기서 생성합니다. 한도 초과가 확인된 경우에만 다음 단계로 넘어가고, 로그인 만료·네트워크 오류는 그대로 실패로 돌려줍니다.
- Typecast 키 — 갱신이 임박한 키부터, 같은 날이면 잔여가 적은 키부터 사용합니다. 잔여가 부족하거나 크레딧 소진(402)이면 다음 키로 넘어갑니다.
동시 요청은 키별 플랜의 동시 생성 수만큼 병렬로 처리되고 초과분은 대기합니다 (예: lite 5건, free 2건, TubeLab 2건). 한 요청은 대사 하나이므로, 여러 대사를 병렬로 만들려면 클라이언트에서 요청을 동시에 보내면 됩니다. 같은 대사·목소리 조합의 동시 요청은 한 번만 생성하고 나머지는 그 결과를 공유합니다.
오류
{ "error": "사람이 읽는 메시지", "code": "MACHINE_CODE" }| 상태 | code | 설명 |
|---|---|---|
| 400 | INVALID_REQUEST | 본문 형식 오류. details에 필드별 사유가 담깁니다. |
| 401 | TOKEN_REQUIRED / INVALID_TOKEN | Bearer 토큰 누락 또는 불일치. |
| 403 | TYPECAST_CREDITS_EXHAUSTED | TubeLab 한도와 모든 키의 잔여가 부족합니다. 메시지에 키별 사유가 들어갑니다. |
| 404 / 410 | MEDIA_NOT_FOUND / MEDIA_EXPIRED | 오디오가 없거나 보관 기간(7일)이 지났습니다. |
| 409 | IDEMPOTENCY_CONFLICT | 같은 requestId를 다른 내용으로 재사용했습니다. |
| 422 | TEXT_REQUIRED / TEXT_TOO_LONG / VOICE_REQUIRED / INVALID_SPEED | 값 검증 실패. |
| 503 | SPEECH_RECONCILING | 직전 요청의 결제 여부를 확인 중입니다. details.retryAfterSeconds 뒤에 재시도하세요. |
| 503 | NO_ROUTE_AVAILABLE | 등록된 TubeLab 세션도 Typecast 키도 없습니다. |
| 502 / 503 | TYPECAST_UNAVAILABLE / TUBELAB_UNAVAILABLE | 공급자 오류. 프록시가 지정된 키는 직접 연결로 우회하지 않습니다. |
재시도할 때는 같은 requestId를 그대로 보내세요. 결제되었을 수 있는 실패를 다른 키로 다시 생성해 이중 과금되는 일을 막습니다.