ListenHubSDKs & CLI
JavaScript SDK

Authentication

Authenticate the SDK with an API key for server-side work, or with OAuth user tokens when acting on a signed-in user's behalf.

The SDK ships two clients, and they authenticate differently. Pick the one that matches where your code runs.

OpenAPIClientListenHubClient
CredentialAPI key (lh_sk_…)OAuth user access token
Header sentAuthorization: Bearer <apiKey>Authorization: Bearer <accessToken>
Acts asYour account / key ownerThe signed-in user
Runs inServers, scripts, CIUser-facing apps
Base URLhttps://api.marswave.ai/openapihttps://api.listenhub.ai/api

API keys are secrets with full access to your account. Keep them on the server. Never ship a key in browser, mobile, or any client-side bundle. For user-facing apps, use ListenHubClient with OAuth so each request runs under the user's own account.

API key (OpenAPIClient)

Use OpenAPIClient for server-side and automation work. It targets the public OpenAPI surface and authenticates with a single API key that represents your account.

Create a key

Generate a key in the dashboard at listenhub.ai/settings/api-keys. Keys have the format lh_sk_<keyId>_<secret>. The secret is shown once at creation — copy it then, and store it somewhere only your server can read.

Construct the client

Pass the key explicitly, or set LISTENHUB_API_KEY and construct with no arguments. If neither is present, the constructor throws.

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

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

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

Run it with the key in the environment:

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
});

Prefer reading the key from a secret manager or environment variable over hardcoding it. The example still pulls from the environment — never paste a literal lh_sk_… string into source you commit.

On every request the client sets Authorization: Bearer <apiKey> for you. There is no token to refresh — the key is valid until you rotate or delete it.

Raw HTTP equivalent

If you are not using the SDK, send the same header against the OpenAPI base URL:

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

Rotating a key

If a key leaks, delete it in the dashboard and issue a new one. Deletion takes effect immediately — any request still using the old key starts failing with an auth error (see error handling). Roll the replacement key into your secret store and redeploy.

OAuth user tokens (ListenHubClient)

Use ListenHubClient when each request must run as a specific signed-in user — for example a desktop tool or a CLI where users log in with their own ListenHub account. Instead of an API key, this client carries an OAuth access token that you obtain through a short browser flow.

The flow

ListenHubClient exposes four auth methods that map to the standard authorize → exchange → refresh → revoke lifecycle:

MethodPurpose
connectInit({ callbackPort })Start a login session. Returns sessionId and an authUrl to open in the browser.
connectToken({ sessionId, code })Exchange the callback code for tokens. Returns accessToken, refreshToken, expiresIn.
refresh({ refreshToken })Mint a fresh access token when the current one expires. Returns the same token shape.
revoke({ refreshToken })Invalidate the refresh token (sign out).

The login client itself needs no credentials, so construct a bare ListenHubClient to drive connectInit / connectToken. The flow:

Start a local server to receive the OAuth callback, then call connectInit with that port. Open the returned authUrl in the user's browser.

The user authorizes in the browser. ListenHub redirects to http://127.0.0.1:<callbackPort>/?code=<code>. Read code off that request.

Call connectToken({ sessionId, code }) to exchange the code for accessToken, refreshToken, and expiresIn (seconds until the access token expires). Persist both tokens.

Construct an authenticated client with the 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();

The callback server is a few lines of node:http — it listens on an ephemeral port and resolves when the redirect arrives:

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 };
}

Keeping the token fresh

An access token expires after expiresIn seconds. Two ways to stay authenticated.

Pass a static token when the client is short-lived (one script run, one request batch):

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

Pass a getter when the client is long-lived. accessToken accepts () => string | undefined, and the SDK calls it before every request. Keep your current token in a variable, refresh it on the side, and the getter always hands back the latest value:

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 returns a new accessToken and a new refreshToken. Persist both — store the latest refreshToken so the user stays signed in across restarts, and reuse it for the next refresh.

Signing out

Call revoke with the refresh token to invalidate the session server-side, then discard your stored tokens locally:

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

Where credentials live

  • API keys belong in server environment variables or a secret manager — read them through process.env.LISTENHUB_API_KEY. They must never reach a browser or be committed to source control.
  • OAuth tokens are per-user. Store the accessToken and refreshToken somewhere private to that user's session — for a CLI or desktop tool, a 0600-permission file under the user's config directory. Treat the refreshToken as sensitive: it can mint new access tokens until revoked.

Error handling

When authentication fails — a deleted key, an expired token, a revoked session — both clients throw a 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 carries status, code, and requestId. A 401 or 403 means the credential was rejected — for an API key, rotate it; for an OAuth token, run refresh, and if that also fails, send the user back through the login flow. Rate-limited (429) requests are retried automatically with Retry-After, up to maxRetries (default 2), so they rarely surface as errors.

Next steps

On this page