Typecast Gateway API

한 번의 요청으로 대사 하나를 음성으로 만듭니다. 모든 엔드포인트는 /api/v1 아래에 있고 JSON으로 응답합니다.

인증

모든 요청에 관리자 페이지에서 발급한 토큰이 필요합니다.

Authorization: Bearer tcg_xxxxxxxxxxxxxxxxxxxxxxxx

토큰이 없으면 401 TOKEN_REQUIRED, 값이 틀리면 401 INVALID_TOKEN이 돌아옵니다. 토큰 값은 해시로만 저장되므로 분실하면 새로 발급해야 합니다.

POST/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*stringTypecast 목소리 ID. /api/v1/voices 에서 조회합니다.
emotionstring감정 프리셋. 기본 normal. 목소리가 지원하지 않는 값은 normal로 대체합니다.
speednumber0.5 ~ 2.0, 기본 1. 소수 첫째 자리로 반올림합니다.
modelstring | null엔진 버전(ssfm-v30, ssfm-v21). 생략하면 ssfm-v30으로 생성합니다.
speakerstring | null화자 라벨. TubeLab 경로에만 전달되며 결과 음성에는 영향이 없습니다.
requestIdstring | 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
}
응답
필드타입설명
audioSrcstring오디오 다운로드 경로. 이 서비스 기준 상대 경로입니다.
mediaIdstring오디오 식별자(UUID).
durationMsnumber음성 길이(밀리초).
charsnumber과금 기준 글자 수.
provider"tubelab" | "typecast"실제로 생성에 사용한 경로.
keyId / keyLabelstring | nullTypecast 키로 생성한 경우 그 키. TubeLab이면 null.
cachedbooleantrue면 기존 음성을 재사용했고 크레딧을 쓰지 않았습니다.
maxCharsnumber한 요청의 글자 수 상한.
GET/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
GET/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": "..."
}

목소리마다 지원하는 modelsemotions가 다릅니다. 생성 요청의 model·emotion은 이 목록 안의 값이어야 합니다.

GET/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 } 형태이고, 특정 키의 사용량 조회가 실패하면 usagenull, usageError에 이유가 담깁니다.

생성 경로와 순서

  1. 캐시voiceId·text·emotion·speed·model이 같은 음성이 이미 있으면 그대로 반환합니다 (cached: true, 크레딧 소모 없음). 돈을 쓰기 전에 모든 경로의 캐시를 먼저 확인하므로, 예전에 TubeLab으로 만든 음성도 재사용됩니다.
  2. TubeLab — 오늘 남은 문자 수가 충분하면 여기서 생성합니다. 한도 초과가 확인된 경우에만 다음 단계로 넘어가고, 로그인 만료·네트워크 오류는 그대로 실패로 돌려줍니다.
  3. Typecast 키 — 갱신이 임박한 키부터, 같은 날이면 잔여가 적은 키부터 사용합니다. 잔여가 부족하거나 크레딧 소진(402)이면 다음 키로 넘어갑니다.

동시 요청은 키별 플랜의 동시 생성 수만큼 병렬로 처리되고 초과분은 대기합니다 (예: lite 5건, free 2건, TubeLab 2건). 한 요청은 대사 하나이므로, 여러 대사를 병렬로 만들려면 클라이언트에서 요청을 동시에 보내면 됩니다. 같은 대사·목소리 조합의 동시 요청은 한 번만 생성하고 나머지는 그 결과를 공유합니다.

오류

{ "error": "사람이 읽는 메시지", "code": "MACHINE_CODE" }
상태code설명
400INVALID_REQUEST본문 형식 오류. details에 필드별 사유가 담깁니다.
401TOKEN_REQUIRED / INVALID_TOKENBearer 토큰 누락 또는 불일치.
403TYPECAST_CREDITS_EXHAUSTEDTubeLab 한도와 모든 키의 잔여가 부족합니다. 메시지에 키별 사유가 들어갑니다.
404 / 410MEDIA_NOT_FOUND / MEDIA_EXPIRED오디오가 없거나 보관 기간(7일)이 지났습니다.
409IDEMPOTENCY_CONFLICT같은 requestId를 다른 내용으로 재사용했습니다.
422TEXT_REQUIRED / TEXT_TOO_LONG / VOICE_REQUIRED / INVALID_SPEED값 검증 실패.
503SPEECH_RECONCILING직전 요청의 결제 여부를 확인 중입니다. details.retryAfterSeconds 뒤에 재시도하세요.
503NO_ROUTE_AVAILABLE등록된 TubeLab 세션도 Typecast 키도 없습니다.
502 / 503TYPECAST_UNAVAILABLE / TUBELAB_UNAVAILABLE공급자 오류. 프록시가 지정된 키는 직접 연결로 우회하지 않습니다.

재시도할 때는 같은 requestId를 그대로 보내세요. 결제되었을 수 있는 실패를 다른 키로 다시 생성해 이중 과금되는 일을 막습니다.