> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.beatapi.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.beatapi.io/_mcp/server.

# BeatAPI 빠른 시작

> BeatAPI 키를 만들고 첫 이미지·영상 작업을 제출한 뒤, 결과를 폴링하고 로컬 미디어를 업로드하고 웹훅을 추가하며 운영 환경의 재시도를 안전하게 처리하세요.

# 빠른 시작

BeatAPI는 텍스트, 워크플로, 실시간, 영상, 이미지, 이펙트 API 전반에서 서버 측 API 키 하나를 사용합니다. 아래 세 단계로 시작한 다음, 동기식 텍스트 응답, 공통 작업 필드, 업로드, 제한, 운영 환경 오류는 이 가이드의 나머지 부분을 참고하세요.

## 세 단계로 첫 작업 만들기

### 1. API 키 만들기

[대시보드 → API Keys](https://beatapi.io/dashboard/apikeys)에서 키를 만든 다음, 명령 기록에 남지 않도록 셸에 불러오세요.

```bash
read -rsp "BeatAPI API key: " BEATAPI_API_KEY && echo
export BEATAPI_API_KEY
```

모든 요청은 `https://api.beatapi.io` 기본 URL과 다음 헤더를 사용합니다.

```http
Authorization: Bearer <BEATAPI_API_KEY>
```

### 2. 작업 만들기

이 최소 이미지 요청은 `POST /v1/images/tasks`를 사용합니다.

```bash
curl https://api.beatapi.io/v1/images/tasks \
  -X POST \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana",
    "prompt": "Editorial product photograph on a warm stone pedestal."
  }'
```

올바른 요청은 `201 Created`를 반환합니다. 반환된 `data.id`를 저장하세요.

### 3. 결과 폴링

```bash
curl https://api.beatapi.io/v1/tasks/task_8K2qA \
  -H "Authorization: Bearer $BEATAPI_API_KEY"
```

`task_8K2qA`를 2단계의 `data.id`로 바꾸세요. 폴링 사이에는 `data.poll_after_seconds`만큼 기다리세요(이미지 5, 영상과 이펙트 8, 데이터와 워크플로 10. 작업이 실행되는 동안 표시됩니다). `succeeded`이면 `data.output.media`를 읽고 폴링을 멈추세요. `failed`이면 `data.error_code`와 `data.error_message`를 읽고 폴링을 멈추세요.

> **Note**
>
> 영구 API 키는 신뢰할 수 있는 서버에만 두세요. 브라우저 코드, 모바일 코드, 스크립트, 로그, 스크린샷에는 절대 넣지 마세요. 실시간 브라우저에는 서버에서 만든 수명이 짧은 `client_secret`만 전달됩니다.

## API 선택

| API          | 생성 작업                            | 입력                                   | 결과                       |
| ------------ | -------------------------------- | ------------------------------------ | ------------------------ |
| 텍스트(GPT-5.6) | `POST /v1/responses`             | 텍스트, 이미지, 도구, 추론 설정                  | 동기식 JSON 또는 스트리밍 텍스트 응답  |
| 워크플로 — 뮤직비디오 | `POST /v1/music-video/tasks`     | 오디오와 등급별 시각 참조 자료                    | 완성된 뮤직비디오와 스토리보드 메타데이터   |
| 워크플로 — 이커머스  | `POST /v1/ecommerce-video/tasks` | 제품 이미지, 길이, 크리에이티브 브리프               | 완성된 제품 영상                |
| 워크플로 — 영상 분석 | `POST /v1/video-analysis/tasks`  | 업로드한 영상, 분석 프롬프트, 분석 깊이              | 분석 텍스트와 토큰 사용량           |
| 실시간          | `POST /v1/realtime/sessions`     | 브라우저 출처와 고정 길이, 이후 라이브 `MediaStream` | 실시간 생성 WebRTC 영상         |
| 영상           | `POST /v1/videos/tasks`          | 모델별 프롬프트와 미디어 참조                     | 호스팅된 영상 하나               |
| 이미지          | `POST /v1/images/tasks`          | 모델별 프롬프트와 선택적 참조                     | 호스팅된 이미지 하나              |
| 이펙트          | `POST /v1/effects/tasks`         | 게시된 이펙트 ID, 이미지, 지원 옵션               | 호스팅된 이미지 또는 영상 이펙트 결과 하나 |

텍스트 호출은 동기식으로 또는 서버 전송 이벤트 스트림으로 반환됩니다. 실시간 API는 수명이 짧은 세션을 만듭니다. 워크플로, 영상, 이미지, 이펙트 생성 작업은 비동기 작업을 반환합니다. `data.id`를 저장한 뒤 공통 작업 엔드포인트를 폴링하세요. `GET /v1/tasks/{task_id}` 폴링에는 요청 속도 제한이 없으며, 폴링 사이에 `poll_after_seconds`만큼 기다리세요.

GPT 모델 선택, 요청 형식, 스트리밍 예시는 [GPT 가이드](/text-api/gpt/responses)를 참고하세요.

## 작업 상태 조회

`GET /v1/tasks/{task_id}`

| 파라미터      | 위치 | 타입     | 필수 | 규칙                      |
| --------- | -- | ------ | -- | ----------------------- |
| `task_id` | 경로 | string | 예  | 생성 응답의 `data.id`를 사용하세요 |

### 상태 값

| 상태                 | 의미                                   | 클라이언트 조치                                                |
| ------------------ | ------------------------------------ | ------------------------------------------------------- |
| `queued`           | 접수되어 대기 중                            | 폴링을 계속하세요                                               |
| `processing`       | 생성 중                                 | 폴링을 계속하세요                                               |
| `storyboard_ready` | 뮤직비디오 스토리보드가 준비됨                     | 자동 흐름이 계속될 수 있습니다. 필요하면 확인하세요                           |
| `requires_action`  | 뮤직비디오 수동 검토 대기 중                     | 샷을 편집하거나 합성하세요                                          |
| `editing`          | 뮤직비디오 샷 편집 중                         | 폴링을 계속하세요                                               |
| `composing`        | 뮤직비디오 합성 중                           | 폴링을 계속하세요                                               |
| `succeeded`        | 호스팅된 출력이 준비됨                         | `data.output`를 읽고 폴링을 멈추세요                              |
| `failed`           | 작업이 오류로 끝났습니다. 응답은 여전히 HTTP `200`입니다 | `error_code`, `error_message`, `retryable`을 읽고 폴링을 멈추세요 |

이미지와 영상 작업은 `queued`, `processing`, `succeeded`, `failed`만 사용합니다. 나머지 상태는 뮤직비디오 전용입니다.

### 정규화된 작업 필드

| 필드                   | 타입              | 포함 조건                 | 의미                                                                                    |
| -------------------- | --------------- | --------------------- | ------------------------------------------------------------------------------------- |
| `id`                 | string          | 항상                    | 고정된 작업 ID                                                                             |
| `object`             | string          | 항상                    | `task`                                                                                |
| `task_kind`          | string          | 항상                    | `workflow`, `effect`, `image`, `video` 또는 `data`                                      |
| `capability_id`      | string          | 항상                    | 고정된 워크플로, 이펙트 또는 생성 모델 ID                                                             |
| `capability_version` | integer         | 항상                    | 항상 `1`                                                                                |
| `workflow`           | string          | 워크플로 작업               | `music-video` 또는 `ecommerce-video`                                                    |
| `effect_id`          | string          | 이펙트 작업                | 고정된 이펙트 ID                                                                            |
| `effect_version`     | integer         | 이펙트 작업                | 선택된 이펙트의 변경 불가 버전                                                                     |
| `media_type`         | string          | 이미지/영상 작업             | `image` 또는 `video`                                                                    |
| `model`              | string          | 이미지/영상 작업             | 고정된 모델 별칭                                                                             |
| `status`             | string          | 항상                    | 현재 수명 주기 상태                                                                           |
| `stage`              | string          | 항상                    | `status`와 같은 값. 호환성을 위해 유지                                                            |
| `storyboard`         | object          | 뮤직비디오                 | 가능한 경우 샷 메타데이터                                                                        |
| `created_at`         | integer         | 항상                    | Unix 타임스탬프                                                                            |
| `updated_at`         | integer         | 항상                    | Unix 타임스탬프                                                                            |
| `completed_at`       | integer or null | 항상                    | 종료 시점의 Unix 타임스탬프                                                                     |
| `poll_after_seconds` | integer         | 작업 실행 중               | 다음 폴링까지 기다릴 초: 이미지 5, 영상과 이펙트 8, 데이터와 워크플로 10                                         |
| `typical_seconds`    | object          | 작업 실행 중이며, 기록이 충분할 때만 | `p50`, `p90`, `samples`, `window`: 이 모델이 최근에 걸린 시간입니다. `p90`를 넘으면 작업이 오래 걸리고 있는 것입니다. |
| `output`             | object or null  | 항상                    | 성공할 때까지 `null`                                                                        |
| `usage`              | object          | 항상                    | 크레딧과 과금 대상 길이                                                                         |
| `request_id`         | string          | 항상                    | 지원/디버그용 상관 ID                                                                         |
| `error_code`         | string or null  | 항상                    | 기계가 읽을 수 있는 작업 실패 코드                                                                  |
| `error_message`      | string or null  | 항상                    | 사람이 읽을 수 있는 작업 실패 메시지                                                                 |
| `retryable`          | boolean         | 실패한 작업에만 있음           | 작업을 다시 제출하면 성공할 수 있는지 여부                                                              |

### 사용량과 출력 필드

> **Note**
>
> `credits_reserved` 같은 공개 응답 이름은 API 호환성을 위해 유지됩니다. 모든 크레딧과 잔액 값은 소수점 USD 원장 금액입니다: 1 Credit = $1 USD. 대시보드는 같은 값을 `$\` 기호로 표시합니다.

| 필드                                | 타입                | 의미                              |
| --------------------------------- | ----------------- | ------------------------------- |
| `usage.credits_reserved`          | number            | 작업을 위해 보류된 USD 금액               |
| `usage.credits_charged`           | number            | 청구된 USD 금액. 실패한 작업은 `0`         |
| `usage.billable_duration_seconds` | integer, optional | USD 계산에 사용된 길이                  |
| `usage.credits_settled`           | number            | 작업 성공 후 확정된 USD 금액. 실패한 작업은 `0` |
| `usage.credits_refunded`          | number            | 작업 실패 후 환불된 USD 금액              |
| `output.media`                    | object\[]         | 호스팅된 결과 파일                      |
| `output.media[].type`             | string            | `image` 또는 `video`              |
| `output.media[].url`              | string            | 결과 URL. 보관해야 하는 결과는 내려받아 저장하세요. |
| `output.media[].mime_type`        | string            | 결과 MIME 형식                      |
| `output.r2_url`                   | string            | 주 결과의 호스팅 URL                   |

실패한 작업에서 `usage.credits_reserved`는 생성 시 보류된 금액, `usage.credits_charged`와 `usage.credits_settled`는 `0`, `usage.credits_refunded`는 환불된 금액입니다. 접수된 작업은 6가지 코드 중 하나로 실패합니다. `content_policy_violation`과 `bad_request`(재시도 불가), `processing_unavailable`, `processing_timeout`, `processing_failed`, `result_transfer_failed`(재시도 가능)입니다. [오류](/errors#where-a-failure-shows-up)를 참고하세요.

## 입력 파일 업로드

`POST /v1/files`

입력이 공개 HTTPS URL에 없고 로컬에 있을 때 파일 업로드를 사용하세요.
모든 업로드에는 정확한 `Content-Length`가 있어야 합니다. 일반적인 `curl`, 브라우저, SDK 업로드는 자동으로 설정합니다. 길이를 선언하지 않은 청크 업로드는 본문을 버퍼링하기 전에 거부됩니다.

### 멀티파트 필드

| 필드        | 타입     | 필수  | 기본값     | 규칙                                                                            |
| --------- | ------ | --- | ------- | ----------------------------------------------------------------------------- |
| `file`    | binary | 예   | —       | 지원되는 파일 하나. 영상은 최대 100 MB, 그 밖의 형식은 최대 50 MB입니다. 100 MB를 넘는 멀티파트 파트는 바로 거부됩니다 |
| `purpose` | string | 아니요 | `input` | 무시됩니다. 응답은 항상 `input`입니다                                                      |

```bash
curl https://api.beatapi.io/v1/files \
  -X POST \
  -H "Authorization: Bearer $BEATAPI_API_KEY" \
  -F "file=@./song.mp3" \
  -F "purpose=input"
```

파일 본문을 해당 자산의 `Content-Type`과 함께 그대로 보낼 수도 있습니다.

| 자산  | 허용 확장자              | 허용 MIME 형식                                                              | 추가 규칙     |
| --- | ------------------- | ----------------------------------------------------------------------- | --------- |
| 이미지 | PNG, JPG/JPEG, WebP | `image/png`, `image/jpeg`, `image/webp`                                 | 최대 50 MB  |
| 오디오 | MP3, WAV, AAC, M4A  | `audio/mpeg`, `audio/wav`, `audio/aac`, `audio/mp4`                     | 최대 50 MB  |
| 자막  | SRT                 | `application/x-subrip`. `text/plain`은 파일 이름이 `.srt`로 끝나는 멀티파트 업로드에서만 허용 | 최대 50 MB  |
| 영상  | MP4, MOV            | `video/mp4`, `video/quicktime`                                          | 최대 100 MB |

흔히 쓰이는 일부 별칭은 받아서 표준 형식으로 저장합니다. 예를 들어 `image/jpg`는 `image/jpeg`으로, `audio/mp3`는 `audio/mpeg`로, `text/plain`(`.srt` 파일 이름)은 `application/x-subrip`으로 저장합니다. PDF, 일반 텍스트, octet-stream, 지원하지 않는 영상 컨테이너, ZIP 업로드는 `400 bad_request`로 거부됩니다. 게이트웨이는 선언된 `Content-Type`을 신뢰하며, 파일 이름에 확장자가 있으면 그 형식과 일치해야 합니다. 파일 내용은 검사하지 않으며 길이나 크기도 읽지 않습니다. 업로드에서 받은 파일이라도 작업을 만들 때 그 파일을 쓰는 모델이나 워크플로가 길이나 크기 규칙 등으로 거부할 수 있습니다. 워크플로 입력 URL은 localhost, 사설 네트워크 또는 데이터 URL일 수 없습니다.

업로드 거부는 다음 메시지 중 하나와 함께 `400 bad_request`로 반환됩니다.

| 메시지                                                                                                         | 원인                                              |
| ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `Content-Length is required for file uploads.`                                                              | 선언된 길이가 없습니다.                                   |
| `Content-Length must be a positive integer.`                                                                | 선언된 길이가 잘못되었습니다.                                |
| `File exceeds the declared upload limit.`                                                                   | 선언된 길이가 업로드 제한을 넘습니다.                           |
| `No file provided. Use multipart "file" field or a raw body.`                                               | 요청에 파일이 없습니다.                                   |
| `The uploaded file is empty.`                                                                               | 0바이트입니다.                                        |
| `Unsupported file type. Supported uploads are png, jpg, jpeg, webp, mp3, wav, aac, m4a, mp4, mov, and srt.` | 허용되지 않는 형식이나 확장자입니다.                            |
| `File exceeds the 50 MB limit.`                                                                             | 50 MB 초과(영상은 `File exceeds the 100 MB limit.`). |
| `File exceeds the 100 MB upload limit.`                                                                     | 100 MB를 넘는 멀티파트 파트.                             |
| `Could not read the uploaded file.`                                                                         | 파일 본문을 읽을 수 없습니다.                               |
| `Could not read the multipart upload.`                                                                      | 멀티파트 양식을 파싱할 수 없습니다.                            |

파일을 저장할 수 없으면 업로드는 `processing_unavailable`("File storage is temporarily unavailable. Retry later; nothing was stored.") 또는 `result_transfer_failed`("The file could not be stored on our side. Retry; nothing was stored.")와 함께 `503`으로 응답합니다. 백오프를 두고 재시도하세요.

### 파일 응답

```json
{
  "data": {
    "id": "file_6f1c2b9e-4d0a-4c58-9a57-2f0a8f3d7b11",
    "object": "file",
    "url": "https://files.beatapi.io/u/6f1c2b9e4d0a4c589a572f0a8f3d7b11.mp3",
    "key": "u/6f1c2b9e4d0a4c589a572f0a8f3d7b11.mp3",
    "mime_type": "audio/mpeg",
    "size_bytes": 1048576,
    "purpose": "input",
    "created_at": 1782210000
  }
}
```

| 필드           | 타입      | 포함 조건 | 의미                                                          |
| ------------ | ------- | ----- | ----------------------------------------------------------- |
| `id`         | string  | 항상    | 파일 ID, `file_<uuid>`                                        |
| `object`     | string  | 항상    | `file`                                                      |
| `url`        | string  | 항상    | HTTPS 입력 URL                                                |
| `key`        | string  | 항상    | 스토리지 키, `u/<random>.<ext>`                                  |
| `mime_type`  | string  | 항상    | 파일이 저장되고 제공되는 표준 MIME 형식(예: `image/jpg`는 `image/jpeg`으로 표시) |
| `size_bytes` | integer | 항상    | 업로드된 바이트 크기                                                 |
| `purpose`    | string  | 항상    | `input`                                                     |
| `created_at` | integer | 항상    | Unix 타임스탬프                                                  |

모델이나 워크플로 요청에는 `data.url`을 사용하세요. 모델이나 워크플로는 작업을 만들 때 자체 제한을 검사합니다. 예를 들어 Standard 뮤직비디오는 10~~180초, Premium은 10~~300초 길이의 오디오를 받습니다.

## 사용량과 한도 확인

`GET /v1/usage`

API 키나 계정의 [개인 액세스 토큰](/personal-access-token)을 보내세요. 둘 다 같은 계정 전체 요약을 반환합니다.

유일한 선택 쿼리 파라미터 `period`는 집계와 세부 내역의 기간(현재 시점까지)을 정합니다: `24h`, `7d`, `30d` 또는 `all`(기본값, 계정 전체 기록). `credit_balance`와 `concurrency`는 항상 현재 값입니다. 그 밖의 파라미터나 다른 `period` 값은 `400`으로 거부됩니다.

```bash
curl "https://api.beatapi.io/v1/usage?period=7d" \
  -H "Authorization: Bearer $BEATAPI_API_KEY"
```

| 필드                   | 타입                | 의미                                       |
| -------------------- | ----------------- | ---------------------------------------- |
| `object`             | string            | `usage`                                  |
| `period`             | string            | `24h`, `7d`, `30d` 또는 `all`              |
| `since`, `until`     | integer, optional | 기간의 시작·끝(Unix 초). `all`에서는 없음            |
| `account_name`       | string, optional  | 계정 표시 이름                                 |
| `credit_balance`     | number            | 현재 USD 잔액. 음수일 수 있음                      |
| `total_tasks`        | integer           | 계정 작업 수                                  |
| `credits_settled`    | number            | 정산된 USD 금액                               |
| `credits_refunded`   | number            | 환불된 USD 금액                               |
| `concurrency`        | object            | 처리 중 작업 용량 요약                            |
| `concurrency.limit`  | integer           | 계정의 처리 중 작업 한도                           |
| `concurrency.active` | integer           | 현재 처리 용량을 사용 중인 작업                       |
| `realtime`           | object, optional  | 실시간 세션 사용량 요약                            |
| `realtime.sessions`  | integer           | 실시간 세션 총수                                |
| `realtime.credits`   | number            | 실시간 정산 USD 금액                            |
| `realtime.active`    | integer           | `ready`, `connecting` 또는 `active` 상태의 세션 |
| `by_workflow[]`      | object\[]         | 워크플로 호환성 세부 내역                           |
| `by_capability[]`    | object\[]         | 고정 기능별 전체 작업 종류                          |
| `by_model[]`         | object\[]         | 모델 별칭별 이미지/영상 작업                         |
| `by_api_key[]`       | object\[]         | API 키별 작업/크레딧 합계                         |

세부 항목 필드:

| Array           | 필수 항목 필드                                                        |
| --------------- | --------------------------------------------------------------- |
| `by_workflow`   | `workflow`, `tasks`, `credits_settled`                          |
| `by_capability` | `task_kind`, `capability_id`, `tasks`, `credits_settled`        |
| `by_model`      | `media_type`, `model`, `tasks`, `credits_settled`               |
| `by_api_key`    | `api_key_id`, `title`, `key_prefix`, `tasks`, `credits_settled` |

`storyboard_ready` 또는 `requires_action`에서 일시 중지된 뮤직비디오 작업은 처리 중 작업으로 집계되지 않으면서도 정산된 크레딧이 있을 수 있습니다.

작업이나 텍스트 요청별 비용은 계정의 [개인 액세스 토큰](/personal-access-token)으로 요청별 사용 기록을 읽어 확인하세요.

## 오류와 재시도 정책

```json
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded: at most 60 successful requests per 1 minute(s). Retry after 60 seconds.",
    "retryable": true,
    "request_id": "req_abc123",
    "retry_after_seconds": 60
  }
}
```

모든 오류에는 `code`, 원인을 설명하는 영어 `message`, `request_id`가 있습니다. `429 rate_limit_exceeded`에만 `retry_after_seconds`와 `Retry-After` 헤더가 추가로 있으며, `503`은 대기 시간을 알 수 없으므로 백오프를 두고 재시도하세요. 아래 표의 모든 코드에는 `retryable`이 있습니다. 이 값으로 분기하세요. `false`는 요청을 바꾸기 전까지 같은 호출이 다시 실패한다는 뜻입니다.

### 공개 오류 코드

BeatAPI는 아래의 고정된 공개 코드만 노출합니다. BeatAPI는 모델 뒤의 공급사를 밝히지 않으며 원본 응답 본문을 그대로 전달하지 않습니다. [오류](/errors) 페이지에서 작업 생성 시와 접수된 작업에서 각 실패가 어떤 코드를 내는지, 텍스트 엔드포인트가 모델 호출 전(이 형식)과 호출 중(SDK 고유의 오류 형식. BeatAPI 자체 거부에는 BeatAPI 코드, 모델 측 실패에는 변환된 코드)에 어떻게 실패하는지 확인할 수 있습니다. 이 값은 응답의 `error.code`에 나타납니다. 비동기 작업이 최종 실패하면 작업의 `error_code`에도 해당 값이 반환될 수 있습니다.

| 코드                              | 의미                                                                                                                 | 클라이언트 조치                                                            |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `bad_request`                   | JSON 형식이 잘못되었거나 요청 필드, 조합, 형식, URL, 제한이 올바르지 않습니다. 작업 생성 시이거나, 접수된 작업에서 모델이 파라미터나 입력을 거부한 경우입니다. 메시지에 해당 필드가 표시됩니다 | 재시도 전에 요청을 수정하세요                                                    |
| `missing_api_key`               | API 키를 보내지 않았습니다                                                                                                   | `Authorization: Bearer <key>`를 보내세요                                 |
| `invalid_api_key`               | 키가 잘못되었거나 만료·비활성화·소진되었습니다. 메시지에 어느 경우인지 표시됩니다                                                                      | 대시보드에서 활성 키를 확인하세요                                                  |
| `unauthorized`                  | `401`의 이전 이름으로, 2026-09-29에 `missing_api_key`와 `invalid_api_key`로 대체되었습니다. 아직 나타날 수 있습니다                           | `invalid_api_key`처럼 처리하세요                                           |
| `forbidden`                     | 키나 계정이 이 작업을 수행할 수 없습니다. 예: 무료 크레딧으로 쓸 수 없는 모델, 키의 IP 허용 목록, 키의 그룹                                                 | 메시지를 확인하세요. 무료 크레딧 계정은 충전하면 모든 모델을 쓸 수 있습니다                         |
| `not_found`                     | 알 수 없는 모델이나 기능, 또는 존재하지 않거나 이 API 키의 계정 소유가 아닌 리소스                                                                 | ID와 API 키 소유 계정을 확인하세요                                              |
| `insufficient_credits`          | 잔액 부족: 계정 잔액이 0이거나 이 요청 가격보다 적습니다                                                                                  | 유료 요청을 다시 보내기 전에 충전하세요                                              |
| `idempotency_conflict`          | 멱등성 키가 이미 다른 요청 본문에 사용되었습니다                                                                                        | 같은 논리적 요청에만 키를 재사용하거나 새 키를 사용하세요                                    |
| `rate_limit_exceeded`           | API 키가 요청 속도 제한(무료 모델 제한 포함)을 넘었습니다                                                                                | `Retry-After` 또는 `retry_after_seconds`를 따르세요                        |
| `content_policy_violation`      | 프롬프트, 입력 미디어 또는 결과가 콘텐츠 검수에서 거부되었습니다                                                                               | 새 요청을 제출하기 전에 프롬프트나 미디어를 수정하세요                                      |
| `processing_unavailable`        | 모델을 일시적으로 사용할 수 없거나 용량이 가득 찼습니다(요청 속도 제한과는 무관)                                                                     | 나중에 백오프를 두고 재시도하세요. 이미 접수된 작업은 계속 폴링하세요                             |
| `processing_failed`             | BeatAPI 측 원인으로 처리에 실패했습니다                                                                                          | 폴링을 멈추고 `error_message`와 환불 필드를 확인한 뒤 백오프를 두고 재시도하세요                |
| `processing_timeout`            | 처리 시간 안에 처리가 끝나지 않았습니다                                                                                             | 폴링을 멈추고 환불 필드를 확인한 뒤, 필요하면 새 요청을 제출하세요                              |
| `result_transfer_failed`        | BeatAPI가 생성 결과를 자체 스토리지에 저장하지 못했습니다                                                                                | 폴링을 멈추고 환불 필드를 확인한 뒤, 필요하면 작업을 재시도하세요                               |
| `result_expired`                | `operation: "result"`를 사용한 `POST /v1/capabilities/run`에서 저장된 결과를 찾지 못했습니다. 결과는 1시간 동안 보관됩니다(`404`)                 | 기능을 다시 실행하세요                                                        |
| `not_implemented`               | 엔드포인트는 게시되었지만 이 게이트웨이에서 아직 제공하지 않습니다(`501`)                                                                        | 재시도하지 마세요                                                           |
| `realtime_disabled`             | 이 배포 환경에서는 Realtime Video가 활성화되어 있지 않습니다                                                                           | 이 배포 환경에서는 Realtime Video가 활성화되어 있지 않습니다. 직접 조치할 방법이 없으니 지원팀에 문의하세요 |
| `realtime_capacity_unavailable` | 실시간 처리 용량을 일시적으로 사용할 수 없습니다                                                                                        | 잠시 후 백오프를 두고 재시도하세요. 대기 시간은 표시되지 않습니다                               |
| `realtime_session_expired`      | 실시간 세션을 더 이상 사용할 수 없습니다                                                                                            | 새 세션과 클라이언트 시크릿을 만드세요                                               |
| `origin_not_allowed`            | 이 브라우저 출처는 실시간 세션에 허용되지 않습니다                                                                                       | 설정한 HTTPS 출처를 정확히 사용하세요                                             |
| `invalid_client_secret`         | 실시간 클라이언트 시크릿이 잘못되었거나 더 이상 사용할 수 없습니다                                                                              | 새 세션을 만들고 새 클라이언트 시크릿을 사용하세요                                        |
| `transport_not_allowed`         | 요청한 실시간 전송 방식이 허용되지 않습니다                                                                                           | 실시간 API 문서에 나온 전송 방식을 사용하세요                                         |
| `internal_error`                | BeatAPI에서 예기치 않은 내부 오류가 발생했습니다                                                                                     | `request_id`를 보관하세요. 안전할 때만 재시도하거나 BeatAPI 지원팀에 문의하세요               |

| HTTP  | 코드                                                                                            | `retryable` | 클라이언트 조치                                     |
| ----- | --------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------- |
| `400` | `bad_request`, `content_policy_violation`                                                     | `false`     | 필드, 조합, 형식, URL, 제한 또는 입력을 수정하세요             |
| `401` | `missing_api_key`, `invalid_api_key`                                                          | `false`     | Bearer 키를 보내거나 교체하세요                         |
| `402` | `insufficient_credits`                                                                        | `false`     | 새 유료 요청 전에 충전하세요                             |
| `403` | `forbidden` 또는 실시간 출처 오류                                                                      | `false`     | 권한이나 정확한 브라우저 출처를 확인하세요                      |
| `404` | `not_found`, `result_expired`                                                                 | `false`     | 모델, 레퍼런스 또는 ID와 API 키 소유 계정을 확인하세요           |
| `409` | `idempotency_conflict`                                                                        | `false`     | 같은 논리적 요청 본문에만 키를 재사용하세요                     |
| `429` | `rate_limit_exceeded`                                                                         | `true`      | `Retry-After` 또는 `retry_after_seconds`를 따르세요 |
| `500` | `internal_error`                                                                              | `true`      | `request_id`를 보관하고 한 번 재시도한 뒤 지원팀에 문의하세요     |
| `501` | `not_implemented`                                                                             | `false`     | 재시도하지 마세요                                    |
| `503` | `processing_unavailable`, `processing_timeout`, `processing_failed`, `result_transfer_failed` | `true`      | 백오프를 두고 재시도하세요                               |

BeatAPI는 `502`나 `504`로 응답하지 않습니다. 이런 응답을 받았다면 네트워크 에지에서 온 것이며 JSON 본문이 없으므로 `503`처럼 재시도하세요.

멱등성:

* 워크플로, 이미지, 영상, 이펙트 작업 생성과 `POST /v1/capabilities/run`은 선택적으로 키를 받습니다. 실시간 세션 생성에는 키가 필수입니다(최대 255자).
* 키는 24시간 동안 기억됩니다. 255자를 넘는 키는 `400 bad_request`입니다.
* 같은 키를 다른 본문이나 경로에 사용하면 `409 idempotency_conflict`입니다. 첫 요청이 아직 처리 중일 때 같은 키를 사용해도 마찬가지입니다.
* 완전히 같은 요청을 반복하면 `Idempotent-Replayed: true` 헤더와 함께 저장된 응답이 반환됩니다.
* `2xx` 응답만 저장되므로 `400`이나 `402` 뒤에는 같은 키로 재시도할 수 있습니다.

## 운영 환경 체크리스트

* 정확한 `https://api.beatapi.io` 오리진을 사용하세요.
* 영구 API 키는 신뢰할 수 있는 서버에만 두세요.
* 선택한 API 페이지에 따라 필수/선택 필드, 열거값, 제한, 조건부 규칙을 검증하세요.
* 백그라운드 작업을 시작하기 전에 작업/세션 ID와 `request_id`를 저장하세요.
* `poll_after_seconds`마다 폴링하고, 문서에 나온 종료 상태나 조치 필요 상태에서 멈추세요.
* 보관해야 하는 결과는 내려받아 저장하세요.
* 상태 코드, `Retry-After`, `retry_after_seconds`를 따르세요.
* JSON을 파싱하기 전에 원본 바이트로 웹훅 서명을 검증하세요. 웹훅은 `task.succeeded`와 `task.failed`만 보내며 3번 시도합니다(1분 후와 5분 후 재시도). 시간 초과 정리로 실패 처리된 작업은 이벤트를 보내지 않으므로, 폴링을 기준으로 삼으세요.
* 크레딧 보류, 정산, 환불, 오류, 최종 출력 URL을 기록하세요.
* 카메라, 얼굴, 음성, 참조 자료를 처리하기 전에 [개인정보처리방침](https://beatapi.io/privacy-policy)과 [서비스 약관](https://beatapi.io/terms-of-service)을 검토하세요.