설정
두 SDK 클라이언트의 base URL, 타임아웃, 재시도를 설정하고 응답 언래핑, ListenHubError, 429 자동 재시도가 어떻게 동작하는지 알아보세요.
두 SDK 클라이언트 모두 생성자에서 옵션 객체를 받습니다. 둘은 옵션 이름과 응답 처리 방식이 같지만 기본값이 다릅니다 — OpenAPIClient는 서버 사이드 작업을 위해 만들어져 타임아웃이 더 깁니다. 이 페이지에서는 모든 옵션, 클라이언트가 대신 벗겨 주는 응답 엔벨로프, 에러가 ListenHubError로 드러나는 방식, 429 자동 재시도, 그리고 SDK가 아직 감싸지 않은 엔드포인트를 호출하기 위한 client.api 탈출구를 다룹니다.
옵션
ListenHubClient는 ClientOptions를, OpenAPIClient는 OpenAPIClientOptions를 받습니다. 모든 필드는 선택 사항입니다.
| 옵션 | 타입 | OpenAPIClient 기본값 | ListenHubClient 기본값 |
|---|---|---|---|
apiKey | string | 환경 변수 LISTENHUB_API_KEY | —(해당 없음) |
accessToken | string | (() => string | undefined) | —(해당 없음) | 없음(익명) |
baseURL | string | https://api.marswave.ai/openapi | https://api.listenhub.ai/api |
timeout | number(밀리초) | 60000 | 30000 |
maxRetries | number | 2 | 2 |
자격 증명은 클라이언트마다 다릅니다: OpenAPIClient는 apiKey로, ListenHubClient는 accessToken으로 인증합니다. 각각을 얻는 방법은 인증을 참고하세요.
baseURL
클라이언트를 특정 API 호스트로 향하게 합니다. 기본값이 프로덕션을 가리키므로 직접 설정할 일은 거의 없습니다. baseURL을 전달하지 않으면 각 클라이언트는 폴백으로 환경 변수도 읽습니다:
OpenAPIClient→LISTENHUB_OPENAPI_URLListenHubClient→LISTENHUB_API_URL
명시적으로 전달한 baseURL 옵션은 항상 환경 변수보다 우선합니다.
import { OpenAPIClient } from '@marswave/listenhub-sdk';
const client = new OpenAPIClient({
baseURL: 'https://api.marswave.ai/openapi',
});accessToken(ListenHubClient)
accessToken은 정적 문자열 또는 getter 함수 () => string | undefined를 받습니다. getter를 전달하면 SDK가 매 요청 전에 이를 호출하므로, 수명이 긴 클라이언트도 다시 만들지 않고 항상 현재 토큰을 보냅니다. 토큰이 시간이 지나며 갱신되는 경우 이 형태를 권장합니다.
import { ListenHubClient } from '@marswave/listenhub-sdk';
// Static token — fine for short-lived clients
const client = new ListenHubClient({ accessToken: tokens.accessToken });
// Getter — called before each request; return the freshest token you hold
const live = new ListenHubClient({ accessToken: () => store.accessToken });토큰 갱신 생명주기(언제, 어떻게 새 토큰을 발급하는지)는 인증에 있습니다.
apiKey(OpenAPIClient)
apiKey는 서버 사이드 작업용 자격 증명입니다. 명시적으로 전달하거나, 생략하고 생성자가 환경 변수 LISTENHUB_API_KEY를 읽게 하세요. 둘 다 없으면 생성자가 예외를 던집니다.
import { OpenAPIClient } from '@marswave/listenhub-sdk';
const client = new OpenAPIClient({ apiKey: process.env.LISTENHUB_API_KEY });API 키는 계정에 대한 전체 권한을 가진 비밀 정보입니다. 서버 측에만 두세요 — 브라우저나 모바일 코드에 담아 배포하지 마세요. 사용자 대상 앱에서는 OAuth를 쓰는 ListenHubClient를 사용해, 각 요청이 사용자 본인의 계정으로 실행되도록 하세요.
timeout
요청당 타임아웃(밀리초). 이보다 오래 걸리는 요청은 중단되고 reject됩니다. 서버 사이드 생성 호출은 오래 걸릴 수 있으므로 OpenAPIClient의 기본값은 60000(60초)이고, ListenHubClient의 기본값은 30000(30초)입니다.
const client = new OpenAPIClient({ timeout: 120_000 }); // 2 minutes이 타임아웃은 HTTP 요청 자체에 적용되며, 오래 걸리는 생성 작업에는 적용되지 않습니다. 생성은 비동기입니다 — create 호출은 episodeId나 taskId를 빠르게 반환하고, 결과는 폴링해서 가져옵니다. 빠른 시작의 생성 후 폴링 루프를 참고하세요.
maxRetries
클라이언트가 포기하기 전까지 429 Too Many Requests 응답을 재시도하는 횟수입니다. 기본값은 2입니다. 0으로 설정하면 재시도를 비활성화합니다. 재시도되는 것은 429뿐이며, 다른 에러 상태는 즉시 예외를 던집니다. 아래 429 레이트 리밋 재시도를 참고하세요.
const client = new OpenAPIClient({ maxRetries: 0 }); // fail fast, no retry응답 언래핑
모든 ListenHub 응답은 엔벨로프로 감싸여 있습니다:
{ "code": 0, "message": "", "data": { } }code가 0이면 성공이고, 0이 아닌 code는 모두 에러입니다. 두 클라이언트 모두 afterResponse 훅에서 엔벨로프를 대신 처리하므로, 코드에서는 data를 바로 다룰 수 있습니다:
code 0이면 메서드는data로 resolve됩니다 — 래퍼를 볼 일이 없습니다.code가0이 아니면 메서드는 엔벨로프의code,message,request_id를 담은ListenHubError를 던집니다.204 No Content응답(및 JSON이 아닌 모든 body)은 그대로 통과합니다.
// The SDK returns data directly — no envelope to peel
const { items } = await client.listSpeakers({ language: 'en' });일부 메서드는 스트리밍하거나 바이너리를 반환하기 때문에 언래핑된 JSON 대신 원본 Response를 반환합니다 — 예를 들어 TTS 오디오(tts, audioSpeech)와 text-stream 엔드포인트가 그렇습니다. 이들은 예외이고, 나머지는 모두 data로 resolve됩니다.
에러 처리
요청이 실패하면 — 자격 증명 거부, 검증 오류, 0이 아닌 code, HTTP 에러 상태 — 메서드는 ListenHubError를 던집니다. 이를 catch해서 필드를 확인하세요:
| 필드 | 타입 | 의미 |
|---|---|---|
status | number | HTTP 상태 코드(예: 401, 429, 500) |
code | string | 엔벨로프의 백엔드 에러 코드. JSON이 아닌 실패에서는 GATEWAY_ERROR / UNKNOWN_ERROR |
message | string | 사람이 읽을 수 있는 에러 메시지 |
requestId | string | undefined | 요청 식별자 — 지원팀에 문의할 때 함께 전달하세요 |
import { OpenAPIClient, ListenHubError } from '@marswave/listenhub-sdk';
const client = new OpenAPIClient();
try {
const sub = await client.getSubscription();
console.log(sub);
} catch (err) {
if (err instanceof ListenHubError) {
// Structured error from the API
console.error(`[${err.status}] ${err.code}: ${err.message}`);
if (err.requestId) console.error(`request ${err.requestId}`);
if (err.status === 401 || err.status === 403) {
// Credential rejected — rotate the API key, or refresh the OAuth token
}
} else {
// Network failure, timeout, or other non-API error
throw err;
}
}ListenHubError는 JSON이 아닌 실패에 대해서도 던져집니다. 게이트웨이나 프록시가 HTML 에러 페이지를 반환하면 code: 'GATEWAY_ERROR'로 나타나고(페이지의 <title>이 message가 됩니다), 그 밖에 파싱할 수 없는 경우는 code: 'UNKNOWN_ERROR'로 나타납니다. 두 경우 모두 status는 여전히 HTTP 상태를 반영합니다.
429 레이트 리밋 재시도
429 Too Many Requests 응답은 자동으로 재시도되므로 재시도 로직을 직접 작성할 필요가 없습니다. 클라이언트의 동작은 다음과 같습니다:
Retry-After헤더를 읽어 얼마나 기다릴지 결정합니다.- 최대
maxRetries회(기본값2) 재시도합니다. - 모든 재시도를 소진하면
status: 429인ListenHubError를 던집니다.
이 때문에 짧은 레이트 리밋은 대개 저절로 해소되며 에러로 드러나지 않습니다. 429가 즉시 예외가 되기를 원한다면 maxRetries: 0으로 설정하세요. 이 경로를 타는 것은 429뿐이고, 다른 에러 상태는 모두 첫 응답에서 예외를 던집니다.
client.api 탈출구
ListenHubClient는 내부에서 쓰는 ky 인스턴스를 client.api로 노출합니다. SDK가 아직 감싸지 않은 엔드포인트를 호출할 때 사용하세요 — 설정된 클라이언트를 그대로 재사용하므로 동일한 인증 헤더, base URL, 엔벨로프 언래핑, 429 재시도가 그대로 적용됩니다.
import { ListenHubClient } from '@marswave/listenhub-sdk';
const client = new ListenHubClient({ accessToken: tokens.accessToken });
// Paths are relative to baseURL — no leading slash (ky requirement)
const data = await client.api.get('v1/some/new-endpoint').json();
const created = await client.api
.post('v1/some/new-endpoint', { json: { foo: 'bar' } })
.json();client.api에 전달하는 경로는 baseURL을 기준으로 한 상대 경로이며, 앞에 /를 붙이면 안 됩니다 — ky의 요구 사항입니다. /v1/...이 아니라 v1/...을 사용하세요.
이 탈출구는 ListenHubClient에서 사용할 수 있습니다. OpenAPIClient에서는 타입이 지정된 메서드를 우선 사용하세요. 다루지 않는 엔드포인트가 필요하다면 OpenAPI 레퍼런스를 참고해, 동일한 Authorization: Bearer 헤더를 붙여 직접 만든 HTTP 클라이언트로 호출하세요.