ListenHubSDKs & CLI
JavaScript SDK

인증

서버 사이드 작업에서는 API 키로, 로그인한 사용자를 대신해 동작할 때는 OAuth 사용자 토큰으로 SDK를 인증하세요.

SDK는 두 개의 클라이언트를 제공하며, 각각 인증 방식이 다릅니다. 코드가 실행되는 위치에 맞는 쪽을 선택하세요.

OpenAPIClientListenHubClient
자격 증명API 키(lh_sk_…)OAuth 사용자 access token
전송되는 헤더Authorization: Bearer <apiKey>Authorization: Bearer <accessToken>
실행 주체내 계정 / 키 소유자로그인한 사용자
실행 위치서버, 스크립트, CI사용자 대상 앱
Base URLhttps://api.marswave.ai/openapihttps://api.listenhub.ai/api

API 키는 계정에 대한 전체 권한을 가진 비밀 정보입니다. 반드시 서버에만 두세요. 브라우저, 모바일, 그 밖의 어떤 클라이언트 측 번들에도 키를 포함해 배포하지 마세요. 사용자 대상 앱에서는 OAuth를 쓰는 ListenHubClient를 사용해, 각 요청이 사용자 본인의 계정으로 실행되도록 하세요.

API 키(OpenAPIClient)

서버 사이드 작업과 자동화에는 OpenAPIClient를 사용하세요. 공개 OpenAPI 인터페이스를 대상으로 하며, 내 계정을 나타내는 단일 API 키로 인증합니다.

키 발급하기

대시보드 listenhub.ai/settings/api-keys에서 키를 생성하세요. 키 형식은 lh_sk_<keyId>_<secret>입니다. secret은 생성 시 한 번만 표시됩니다 — 그때 바로 복사해서, 내 서버만 읽을 수 있는 곳에 보관하세요.

클라이언트 생성하기

키를 명시적으로 전달하거나, LISTENHUB_API_KEY를 설정한 뒤 인자 없이 생성하세요. 둘 다 없으면 생성자가 예외를 던집니다.

import { OpenAPIClient } from '@marswave/listenhub-sdk';

// Reads process.env.LISTENHUB_API_KEY
const client = new OpenAPIClient();

const { items } = await client.listSpeakers({ language: 'en' });

환경 변수에 키를 넣고 실행하세요:

LISTENHUB_API_KEY=lh_sk_... node app.js
import { OpenAPIClient } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient({
  apiKey: process.env.LISTENHUB_API_KEY, // load from your secret store
});

키는 하드코딩하지 말고 시크릿 매니저나 환경 변수에서 읽는 방식을 권장합니다. 위 예제도 환경 변수에서 가져옵니다 — 커밋되는 소스에 lh_sk_… 리터럴 문자열을 붙여 넣지 마세요.

클라이언트는 모든 요청에 Authorization: Bearer <apiKey>를 대신 설정해 줍니다. 갱신해야 할 토큰은 없습니다 — 키는 교체하거나 삭제하기 전까지 계속 유효합니다.

원본 HTTP 요청 등가물

SDK를 쓰지 않는다면, OpenAPI base URL로 동일한 헤더를 보내세요:

curl https://api.marswave.ai/openapi/v1/speakers/list?language=en \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"

키 교체하기

키가 유출되면 대시보드에서 삭제하고 새 키를 발급하세요. 삭제는 즉시 적용됩니다 — 여전히 이전 키를 쓰는 요청은 인증 에러로 실패하기 시작합니다(에러 처리 참고). 새 키를 시크릿 저장소에 반영하고 재배포하세요.

OAuth 사용자 토큰(ListenHubClient)

각 요청이 특정한 로그인 사용자로 실행되어야 할 때는 ListenHubClient를 사용하세요 — 예를 들어 사용자가 자기 ListenHub 계정으로 로그인하는 데스크톱 도구나 CLI가 그렇습니다. 이 클라이언트는 API 키 대신, 짧은 브라우저 플로우로 획득하는 OAuth access token을 사용합니다.

흐름

ListenHubClient는 표준적인 authorize → exchange → refresh → revoke 생명주기에 대응하는 네 개의 인증 메서드를 제공합니다:

메서드용도
connectInit({ callbackPort })로그인 세션을 시작합니다. sessionId와 브라우저에서 열 authUrl을 반환합니다.
connectToken({ sessionId, code })콜백으로 받은 code를 토큰으로 교환합니다. accessToken, refreshToken, expiresIn을 반환합니다.
refresh({ refreshToken })현재 access token이 만료되면 새 토큰을 발급합니다. 동일한 토큰 구조를 반환합니다.
revoke({ refreshToken })refresh token을 무효화합니다(로그아웃).

로그인용 클라이언트 자체는 자격 증명이 필요 없으므로, connectInit / connectToken을 구동할 때는 아무 옵션 없는 ListenHubClient를 생성하세요. 흐름은 다음과 같습니다:

OAuth 콜백을 받을 로컬 서버를 띄운 다음, 그 포트로 connectInit을 호출하세요. 반환된 authUrl을 사용자의 브라우저에서 엽니다.

사용자가 브라우저에서 인가합니다. ListenHub는 http://127.0.0.1:<callbackPort>/?code=<code>로 리다이렉트합니다. 그 요청에서 code를 읽으세요.

connectToken({ sessionId, code })을 호출해 code를 accessToken, refreshToken, expiresIn(access token 만료까지 남은 초)으로 교환하세요. 두 토큰 모두 저장합니다.

access token으로 인증된 클라이언트를 생성하세요: new ListenHubClient({ accessToken }).

import * as http from 'node:http';
import { ListenHubClient } from '@marswave/listenhub-sdk';

// 1. Login client needs no credentials
const loginClient = new ListenHubClient();

// 2. Stand up a temporary callback server, then start the session
const { port, codePromise, server } = startCallbackServer();
const { authUrl, sessionId } = await loginClient.connectInit({ callbackPort: port });

const open = (await import('open')).default;
await open(authUrl); // user signs in here

// 3. Exchange the callback code for tokens
const code = await codePromise;
const tokens = await loginClient.connectToken({ sessionId, code });
server.close();

// tokens: { accessToken, refreshToken, expiresIn }
// Persist tokens somewhere private to the user's machine.

// 4. Build an authenticated client
const client = new ListenHubClient({ accessToken: tokens.accessToken });
const me = await client.getCurrentUser();

콜백 서버는 node:http 몇 줄이면 됩니다 — 임시 포트를 리스닝하다가 리다이렉트가 도착하면 resolve합니다:

function startCallbackServer() {
  let resolveCode!: (code: string) => void;
  const codePromise = new Promise<string>((r) => (resolveCode = r));

  const server = http.createServer((req, res) => {
    const code = new URL(req.url!, 'http://localhost').searchParams.get('code');
    if (code) {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end('<h1>Login successful! You can close this tab.</h1>');
      resolveCode(code);
    } else {
      res.writeHead(400).end('Missing code');
    }
  });

  // Port 0 → OS assigns a free port
  server.listen(0, '127.0.0.1');
  const port = (server.address() as { port: number }).port;
  return { port, codePromise, server };
}

토큰을 최신 상태로 유지하기

access token은 expiresIn초 후에 만료됩니다. 인증 상태를 유지하는 방법은 두 가지입니다.

정적 토큰 전달 — 클라이언트의 수명이 짧을 때(한 번의 스크립트 실행, 한 묶음의 요청):

const client = new ListenHubClient({ accessToken: tokens.accessToken });

getter 전달 — 클라이언트의 수명이 길 때. accessToken() => string | undefined를 받으며, SDK가 매 요청 전에 이를 호출합니다. 현재 토큰을 변수에 보관하고 별도로 갱신하면, getter는 항상 최신 값을 돌려줍니다:

let current = tokens;

const client = new ListenHubClient({
  // Called before each request — return the freshest token you hold
  accessToken: () => current.accessToken,
});

// Refresh on a timer (or lazily, just before expiry) and swap it in
async function refreshTokens() {
  current = await loginClient.refresh({ refreshToken: current.refreshToken });
}

refresh는 새 accessToken refreshToken을 함께 반환합니다. 둘 다 저장하세요 — 최신 refreshToken을 보관해야 재시작 후에도 사용자의 로그인 상태가 유지되고, 다음 갱신에도 그것을 사용합니다.

로그아웃

refresh token으로 revoke를 호출해 서버 측 세션을 무효화한 뒤, 로컬에 저장된 토큰을 폐기하세요:

await loginClient.revoke({ refreshToken: current.refreshToken });
// Then delete accessToken + refreshToken from local storage.

자격 증명을 두는 곳

  • API 키는 서버 환경 변수나 시크릿 매니저에 두고 process.env.LISTENHUB_API_KEY로 읽으세요. 절대 브라우저에 도달해서도, 소스 관리에 커밋되어서도 안 됩니다.
  • OAuth 토큰은 사용자별로 구분됩니다. accessTokenrefreshToken은 해당 사용자 세션만 접근할 수 있는 곳에 저장하세요 — CLI나 데스크톱 도구라면 사용자 설정 디렉터리 아래의 0600 권한 파일이 적당합니다. refreshToken은 민감 정보로 다루세요: 폐기되기 전까지 새 access token을 계속 발급할 수 있습니다.

에러 처리

인증이 실패하면 — 삭제된 키, 만료된 토큰, 폐기된 세션 — 두 클라이언트 모두 ListenHubError를 던집니다:

import { OpenAPIClient, ListenHubError } from '@marswave/listenhub-sdk';

const client = new OpenAPIClient();

try {
  await client.getSubscription();
} catch (err) {
  if (err instanceof ListenHubError) {
    // err.status   → HTTP status (e.g. 401)
    // err.code     → backend error code from the envelope
    // err.requestId → include this when contacting support
    console.error(`[${err.status}] ${err.message} (request ${err.requestId})`);
  }
}

ListenHubErrorstatus, code, requestId를 담고 있습니다. 401 또는 403은 자격 증명이 거부됐다는 뜻입니다 — API 키라면 교체하고, OAuth 토큰이라면 refresh를 실행하세요. 갱신마저 실패하면 사용자를 로그인 플로우로 다시 보내세요. 레이트 리밋에 걸린(429) 요청은 Retry-After에 따라 maxRetries(기본값 2)까지 자동으로 재시도되므로, 에러로 드러나는 경우는 드뭅니다.

다음 단계

이 페이지의 내용