認証
サーバーサイドの処理では API キーで、サインイン中のユーザーの代理として動作する場合は OAuth ユーザートークンで SDK を認証します。
SDK には 2 つのクライアントがあり、それぞれ認証方法が異なります。コードが動作する場所に合うほうを選んでください。
OpenAPIClient | ListenHubClient | |
|---|---|---|
| 認証情報 | API キー(lh_sk_…) | OAuth ユーザーアクセストークン |
| 送信されるヘッダー | Authorization: Bearer <apiKey> | Authorization: Bearer <accessToken> |
| 実行主体 | 自分のアカウント / キーの所有者 | サインイン中のユーザー |
| 動作環境 | サーバー、スクリプト、CI | ユーザー向けアプリ |
| Base URL | https://api.marswave.ai/openapi | https://api.listenhub.ai/api |
API キーはアカウントへのフルアクセス権を持つ機密情報です。サーバー上に保管してください。ブラウザ、モバイル、その他あらゆるクライアントサイドのバンドルにキーを含めて配布しないでください。ユーザー向けアプリでは OAuth を使う ListenHubClient を利用し、各リクエストがユーザー自身のアカウントで実行されるようにします。
API キー(OpenAPIClient)
サーバーサイドや自動化の処理には OpenAPIClient を使います。公開されている OpenAPI のインターフェースを対象とし、アカウントを表す 1 つの 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.jsimport { 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 のライフサイクルに対応する 4 つの認証メソッドを提供します:
| メソッド | 用途 |
|---|---|
connectInit({ callbackPort }) | ログインセッションを開始します。sessionId と、ブラウザで開く authUrl を返します。 |
connectToken({ sessionId, code }) | コールバックの code をトークンと交換します。accessToken、refreshToken、expiresIn を返します。 |
refresh({ refreshToken }) | 現在のアクセストークンが期限切れになったときに新しいトークンを発行します。同じ形のトークンを返します。 |
revoke({ refreshToken }) | リフレッシュトークンを無効化します(サインアウト)。 |
ログイン用のクライアント自体には認証情報が不要なので、素の ListenHubClient を構築して connectInit / connectToken を実行します。フローは次のとおりです:
OAuth のコールバックを受け取るローカルサーバーを起動し、そのポートを指定して connectInit を呼び出します。返ってきた authUrl をユーザーのブラウザで開きます。
ユーザーがブラウザで認可します。ListenHub は http://127.0.0.1:<callbackPort>/?code=<code> にリダイレクトします。そのリクエストから code を読み取ります。
connectToken({ sessionId, code }) を呼び出し、code を accessToken、refreshToken、expiresIn(アクセストークンが期限切れになるまでの秒数)と交換します。両方のトークンを永続化します。
アクセストークンで認証済みのクライアントを構築します: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 };
}トークンを最新に保つ
アクセストークンは expiresIn 秒後に期限切れになります。認証状態を保つ方法は 2 つあります。
静的なトークンを渡す — クライアントが短命な場合(1 回のスクリプト実行、1 バッチのリクエスト):
const client = new ListenHubClient({ accessToken: tokens.accessToken });ゲッターを渡す — クライアントが長命な場合。accessToken は () => string | undefined を受け付け、SDK がリクエストごとにそれを呼び出します。現在のトークンを変数に保持し、別経路で更新しておけば、ゲッターは常に最新の値を返します:
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 を保存しておけば、再起動をまたいでユーザーのサインイン状態が保たれ、次回の更新にも使えます。
サインアウトする
リフレッシュトークンを指定して revoke を呼び出し、サーバー側でセッションを無効化してから、ローカルに保存したトークンを破棄します:
await loginClient.revoke({ refreshToken: current.refreshToken });
// Then delete accessToken + refreshToken from local storage.認証情報の保存場所
- API キー はサーバーの環境変数またはシークレットマネージャーに置き、
process.env.LISTENHUB_API_KEYから読み取ります。ブラウザに渡ってはならず、ソース管理にコミットしてもいけません。 - OAuth トークン はユーザーごとのものです。
accessTokenとrefreshTokenは、そのユーザーのセッションに閉じた場所に保存してください — CLI やデスクトップツールなら、ユーザーの設定ディレクトリ配下の0600権限のファイルが適しています。refreshTokenは機密情報として扱ってください:失効するまで、新しいアクセストークンを発行し続けられます。
エラー処理
認証に失敗したとき — 削除されたキー、期限切れのトークン、失効したセッション — どちらのクライアントも 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})`);
}
}ListenHubError は status、code、requestId を持ちます。401 や 403 は認証情報が拒否されたことを意味します — API キーならローテーションし、OAuth トークンなら refresh を実行して、それも失敗する場合はユーザーをログインフローに戻してください。レート制限された(429)リクエストは Retry-After に従って最大 maxRetries 回(デフォルト 2)自動でリトライされるため、エラーとして表面化することはほとんどありません。