# 오류 처리

> 오류 응답에서 확인할 필드와 코드별 원인을 알아보세요. 요청을 수정해야 할 때와 재시도할 수 있을 때를 구분해요.

Canonical: https://transcript1.com/ko/docs/errors
Language: ko
Last updated: 2026-10-19

오류는 요청 단계와 처리 단계로 나눠 확인하세요. 요청 오류가 나면 API가 호출을 받아들이지 않은 거예요. 요청은 접수됐지만 변환 도중 실패하면 작업의 `status`가 `failed`로 바뀌어요.

## 오류 응답 읽기

`/v1` API의 요청 오류는 아래 형식으로 반환해요.

```json
{
  "error": {
    "code": "insufficient_minutes",
    "message": "Not enough minutes",
    "retryable": false,
    "action_url": "https://transcript1.com/dashboard/billing",
    "minutes_needed": 20,
    "minutes_remaining": 3
  }
}
```

코드에서 오류를 분기할 때는 `code`를 사용하세요. `message`는 사용자에게 보여 줄 설명이며, 문구가 바뀔 수 있으니 분기 조건으로 쓰지 마세요. `retryable`이 `true`이면 잠시 후 같은 요청을 다시 시도할 수 있어요.

오류 종류에 따라 `retryable` 외에 다음 필드를 함께 반환해요.

| 필드 | 포함되는 오류 | 설명 |
|---|---|---|
| `action_url` | `insufficient_minutes` | 요금제를 구매하거나 분량을 충전하는 페이지 주소 |
| `minutes_needed`, `minutes_remaining` | `insufficient_minutes` | 요청에 필요한 분량과 남은 분량. 필요한 분량을 아직 모르면 `null`이에요. |
| `supported_platforms` | `unsupported_platform` | 지원 플랫폼 목록: `["youtube", "x", "tiktok", "instagram", "facebook", "bluesky", "twitch", "dailymotion", "pinterest", "linkedin", "snapchat"]` |
| `missing_chunks` | `upload_incomplete` | 아직 받지 못한 청크의 인덱스 목록 |
| `max_bytes` | `media_too_large` | 현재 허용되는 최대 파일 크기. 바이트 단위예요. |
| `params` | `invalid_request` | 본문·쿼리 검증에서 문제가 된 필드와 이유를 담은 객체 |

## 요청 단계에서 발생하는 오류

| HTTP 상태 | `code` | 그대로 재시도 | 원인과 대응 |
|---|---|---|---|
| 400, 422 | `invalid_request` | 불가 | 필수 파라미터나 본문 필드가 빠졌거나 값이 잘못됐어요. `diarize`와 `source: "captions"`, 또는 `reuse_recent`와 `webhook_url`을 함께 보낸 경우도 해당해요. 본문이 올바른 JSON인지도 확인하세요. |
| 422 | `invalid_url` | 불가 | YouTube, X, TikTok, Instagram, Facebook, Bluesky 또는 [지원 목록](https://transcript1.com/ko/docs/sources-and-options)에 있는 플랫폼의 영상 링크인지 확인하세요. 지정한 `platform`과 링크가 달라도 이 오류가 나요. |
| 422 | `unsupported_platform` | 불가 | 지정한 `platform`을 지원하지 않아요. |
| 422 | `unsupported_media` | 불가 | 파일 확장자가 지원 목록에 없어요. |
| 401 | `unauthorized` | 불가 | 인증 정보가 없거나 잘못됐거나 만료·폐기됐어요. 업로드 토큰이 잘못됐거나 만료된 경우도 해당해요. |
| 403 | `insufficient_scope` | 불가 | OAuth 토큰에 필요한 `transcriptions:read` 또는 `transcriptions:write` 권한이 없어요. |
| 403 | `email_not_verified` | 불가 | 변환 작업을 시작하려면 먼저 이메일 인증을 마쳐야 해요. |
| 402 | `insufficient_minutes` | 불가 | 사용 가능한 분량이 0이거나 이번 변환에 필요한 분량보다 적어요. |
| 402 | `no_active_plan` | 불가 | 이용 중인 유료 요금제 없이 충전을 시도했어요. 변환 API가 아니라 대시보드 결제 엔드포인트에서 반환하는 오류예요. |
| 404 | `not_found` | 불가 | 해당 작업이 없거나 다른 팀에 속해 있어요. |
| 409 | `upload_incomplete` | 불가 | 누락된 청크를 보낸 뒤 업로드 완료 요청을 다시 보내세요. |
| 409 | `invalid_state` | 불가 | 업로드 대기 상태가 아니에요. 더 이상 청크를 전송하거나 업로드를 완료할 수 없어요. |
| 409 | `idempotency_conflict` | 불가 | 이미 사용한 `Idempotency-Key`에 다른 파라미터를 보냈어요. |
| 413 | `media_too_large` | 불가 | 파일이 허용 크기를 넘었어요. [파일 업로드](https://transcript1.com/ko/docs/uploads)에서 한도를 확인하세요. |
| 413 | `invalid_request` | 불가 | JSON 본문이 1MB를 넘었어요. 파일 크기 초과는 `media_too_large`로 구분해요. |
| 429 | `rate_limited` | 가능 | 분당 요청 한도를 넘었어요. `Retry-After`에 지정된 초만큼 기다리세요. |
| 429 | `too_many_active_jobs` | 가능 | 진행 중인 변환 작업이나 완료 전 업로드가 한도를 넘었어요. `Retry-After`에 지정된 초만큼 기다리세요. |
| 500 | `internal_error` | 가능 | transcript1 서버에서 오류가 났어요. 잠시 후 다시 요청하세요. |
| 503 | `service_unavailable` | 가능 | transcript1에 일시적인 장애가 있어요. 잠시 후 다시 요청하세요. |

브라우저에서 Bearer 토큰 대신 로그인 쿠키로 인증하면 요청 출처도 검사해요. 다른 사이트에서 보낸 요청에는 `403 forbidden_origin`을 반환해요. `Authorization` 헤더로 인증하는 API 호출에는 이 검사를 적용하지 않아요.

`429` 응답에는 항상 `Retry-After` 헤더가 있어요. 값은 다시 요청하기까지 기다릴 시간이며 초 단위예요. 실제 한도는 [요금제와 사용 한도](https://transcript1.com/ko/docs/billing-and-limits)에서 확인하세요.

## 안전하게 재시도하기

- 같은 요청을 그대로 다시 보낼 때는 `retryable`이 `true`인지 확인하세요. `rate_limited`, `too_many_active_jobs`, `internal_error`, `service_unavailable`이 해당해요. `Retry-After`가 있으면 지정한 시간만큼 기다리고, 없으면 재시도 간격을 늘리세요.
- `POST /v1/transcriptions`를 다시 보낼 때는 처음과 같은 `Idempotency-Key` 헤더를 사용하세요. 첫 요청이 이미 성공했더라도 중복 차감을 막을 수 있어요.
- 다른 오류는 원인을 해결하고 요청을 수정한 뒤 다시 보내세요.

## 접수된 작업이 실패한 경우

작업의 `status`가 `failed`이면 객체에 `error: { "code", "message" }`가 들어 있어요. 생성 요청에 응답한 뒤에도 작업이 실패할 수 있어요. 상태를 조회할 때 `error`를 확인하거나 `transcription.failed` [웹훅](https://transcript1.com/ko/docs/webhooks)에서 원인을 읽으세요.

| `error.code` | 원인 | 다시 시도하려면 |
|---|---|---|
| `media_unavailable` | 영상이 없거나 더 이상 접근할 수 없어요. | 그대로 재시도하지 말고 URL을 확인하세요. |
| `media_private` | 비공개 영상이에요. | 공개 게시물이나 공개·일부 공개 YouTube 영상으로 바꾸세요. |
| `login_required` | 영상을 보려면 플랫폼 로그인이 필요해요. | 자동 재시도하지 않는 영구 오류예요. |
| `no_audio` | 영상에 오디오 트랙이 없어요. | 자동 재시도하지 않는 영구 오류예요. |
| `age_restricted` | 연령 제한이 걸려 있어요. | 링크 대신 파일을 업로드하세요. |
| `live_stream` | 방송 중이거나 아직 시작하지 않은 라이브예요. | 방송이 끝난 뒤 다시 시도하세요. |
| `no_captions` | `source`가 `captions`인데 사용할 자막 트랙이 없어요. | `source: "auto"` 또는 `"stt"`로 바꾸세요. |
| `media_too_long` | 음성 텍스트 변환(STT)의 최대 길이인 5시간을 넘었어요. | 파일을 나눠서 보내세요. |
| `unsupported_media` | 파일을 읽지 못했거나 오디오 트랙이 없어요. | 지원 형식의 파일로 바꿔 보내세요. |
| `no_speech` | 음성이 감지되지 않았어요. | 그대로 재시도하지 마세요. |
| `unsupported_language` | 지원하지 않는 언어예요. | 그대로 재시도하지 말고 `language`를 지원하는 코드나 `"auto"`로 바꾸세요. |
| `insufficient_minutes` | 차감 시점에 사용 가능한 분량이 부족해졌어요. | 분량을 충전한 뒤 다시 시도하세요. |
| `fetch_failed` | 미디어 다운로드를 여러 번 시도했지만 실패했어요. | 잠시 후 다시 시도할 수 있어요. |
| `transcription_failed` | 음성 텍스트 변환(STT)을 여러 번 시도했지만 실패했어요. | 잠시 후 다시 시도할 수 있어요. |

작업이 실패하거나 취소되면 비용이 들지 않아요. 이미 차감한 분량은 돌려드려요.
