에러 (Errors)
모든 SDK 에러는 추상 클래스 OpenEchoError를 상속하며, 서버 wire 코드(err.code)와 err.httpStatus·err.message를 항상 보존합니다.
import {
OpenEchoError, CapabilityDenied, UsageCapExceeded, OriginForbidden,
} from "@openecho/partner-sdk";
try {
await oe.broadcast.tts.send({ /* ... */ });
} catch (err) {
if (err instanceof CapabilityDenied) {
// TTS capability 미허가
} else if (err instanceof UsageCapExceeded) {
// 일일 quota 초과
} else if (err instanceof OpenEchoError) {
console.error(err.code, err.httpStatus, err.message);
}
}서버 wire 에러 (14)
서버 wire 코드와 SDK 에러 클래스의 완전한 대응입니다. HTTP 상태는 대표값이며, 실제 값은 서버 응답을 그대로 보존합니다.
| 코드 | HTTP | 클래스 | 설명 |
|---|---|---|---|
| PARTNER_UNAUTHORIZED | 401 | Unauthorized | publicKey 무효 또는 거부 |
| PARTNER_TICKET_UNAUTHORIZED | 401 | TicketUnauthorized | SSE 스트림 티켓 인증 실패 |
| PARTNER_ORIGIN_FORBIDDEN | 403 | OriginForbidden | 허용되지 않은 origin — 도메인 잠금 |
| CAPABILITY_DENIED | 403 | CapabilityDenied | 해당 기능 capability 미허가 |
| TARGET_UNRESOLVED | 404 | TargetUnresolved | targetHandle을 해석할 수 없음 |
| BROADCAST_CONTENT_NOT_READY | 400 | ContentNotReady | 오디오 준비가 완료되지 않은 항목 |
| BROADCAST_INELIGIBLE | 409 | BroadcastIneligible | 현재 방송 불가 상태(동시 방송 등) |
| IDEMPOTENCY_KEY_REUSED | 409 | IdempotencyKeyReused | 동일 키가 다른 페이로드로 재사용됨 |
| TARGET_REVISION_STALE | 409 | TargetRevisionStale | 대상 revision 변경 — expectedRevision 갱신 필요 |
| STREAM_GONE | 410 | SessionExpired | SSE 세션 만료(이벤트 보존 기간 초과) |
| USAGE_CAP_EXCEEDED | 429 | UsageCapExceeded | 일일 TTS quota 초과 |
| RATE_LIMITED | 429 | RateLimited | 요청 속도 제한 초과 |
| STREAM_LIMIT_EXCEEDED | 429 | StreamLimitExceeded | SSE 동시 스트림 수 한도 초과 |
| RATE_LIMIT_BACKEND_UNAVAILABLE | 503 | RateLimitBackendUnavailable | 속도 제한 백엔드 일시 불가 |
auth 에러는 정확한 타입으로 노출됩니다
잘못된 키(401)·미등록 origin(403)·티켓 인증 실패(401)는 각각 Unauthorized·OriginForbidden·TicketUnauthorized로 매핑되며 서버 메시지를 보존합니다. UnknownOpenEchoError로 퇴화하지 않습니다.
클라이언트 측 에러 (3)
다음 세 에러는 서버 wire 코드가 아니라 클라이언트/transport 계층에서 발생하므로 wire 표에 없습니다.
| 클래스 | 발생 시점 | 의미 |
|---|---|---|
MicChunkLoadFailed | mic.start() | MIC 청크 다운로드 실패 — 네트워크 오류 또는 CSP 차단(script-src 확인) |
GatewayMasked | 모든 호출 | 게이트웨이가 2xx + HTML로 4xx를 마스킹(GATEWAY_MASKED) |
InvalidApiResponse | 모든 호출 | 2xx 응답이 JSON이 아님(INVALID_API_RESPONSE) |
멱등성 키
idempotencyKey는 중복 방송을 막는 클라이언트 생성 키입니다.
- 새 방송마다
crypto.randomUUID()로 고유 키를 생성합니다. - 네트워크 오류로 재시도할 때는 동일한 키를 재사용합니다(서버가 멱등 처리).
- 다른 페이로드에 같은 키를 재사용하면
IdempotencyKeyReused(409)가 발생합니다.
Quota · Rate Limit
파트너 앱별 일일 TTS 한도 초과 시 UsageCapExceeded(429)가 발생하며 다음 날 자정(UTC)에 초기화됩니다. 단시간 과도 요청은 RateLimited(429) — 지수 백오프로 재시도합니다. RateLimitBackendUnavailable(503)은 잠시 후 재시도합니다.
Origin 잠금 진단
도메인 잠금(OriginForbidden)은 두 단계로 확인합니다.
- 브라우저 CORS 차단 — 미등록 origin은 preflight에서 차단됩니다. DevTools → Network에서
CORS error확인. 해결: 관리 콘솔에서 origin 추가. - curl로 wire error 확인 — CORS를 우회해 실제
error.code를 확인합니다.
curl -s -X POST "https://your-openecho-api.example.com/api/partner/v1/broadcasts/tts" \
-H "X-OpenEcho-Key: pk_test_..." \
-H "Content-Type: application/json" \
-H "Origin: https://not-registered.example.com" \
-d '{"targetHandle":"grp_main","text":"test","language":"ko","idempotencyKey":"k","partial":false}'Unauthorized(키 오류)와 OriginForbidden(도메인 잠금)은 별개입니다. 응답 error.code로 정확히 구분하세요.
CloudFront /api 마스킹 주의
dev 환경에서 CloudFront는 /api/* 4xx를 SPA index.html(HTTP 200)로 치환할 수 있습니다. 이때 SDK는 null 대신 명시적으로 throw합니다.
- 2xx + HTML →
GatewayMasked - 2xx + 비-JSON →
InvalidApiResponse
운영 환경에서는 CloudFront SPA 오류 응답을 /api/*에서 제외해 이 문제가 발생하지 않아야 합니다.

