Skip to main content
Install the official soundlink package to call the Public API with full TypeScript types. For raw HTTP, see Quickstart. Not sure which approach fits your stack? See Official SDKs.

Prerequisites

  • API key (sk_*) with the scopes you need — Authentication
  • PUBLIC_API_ENABLED for your organization (Soundlink Engineering)
  • Node.js 18+ or an Edge runtime with fetch
Store the key in an environment variable (e.g. SOUNDLINK_API_KEY). Never hardcode keys or ship them to the browser.

1. Install

npm install soundlink

2. Initialize the client

import { Soundlink } from "soundlink";

const soundlink = new Soundlink({
  apiKey: process.env.SOUNDLINK_API_KEY!,
});
You can also pass the key as a string: new Soundlink('sk_your_prefix_your_secret'). The client sends x-api-key on every request. Your organization is determined from the key.

3. Ping and list campaigns

const { data, error, meta } = await soundlink.ping();
if (error) {
  console.error(error.message, meta?.requestId);
  process.exit(1);
}

const { data: campaigns, error: listError } = await soundlink.campaigns.list({
  page: 1,
  pageSize: 100,
  sortBy: "createdAt",
  sortOrder: "desc",
});

if (listError) {
  console.error(listError.code, listError.message);
}

const { data: campaign } = await soundlink.campaigns.get("camp_abc123");
List and get require the campaigns:read scope. Equivalent curl examples are in Quickstart.

Paginate all campaigns

for await (const campaign of soundlink.campaigns.listAll({ pageSize: 100 })) {
  console.log(campaign.campaignId);
}

Response pattern

Every method returns { data, error, meta? } — the same envelope as the REST API. HTTP errors are not thrown; check error and use requestId when contacting support.
Do not use try/catch for normal API failures. The SDK only throws for configuration, parse, or transport errors. See Errors below and Errors for API codes.

Metrics and exports

const { data: overview } = await soundlink.metrics.overview("camp_abc123", {
  startDate: "2026-01-01",
  endDate: "2026-03-31",
});

const { data: stream } = await soundlink.metrics.breakdown.export(
  "camp_abc123",
  {
    startDate: "2026-01-01",
    endDate: "2026-03-31",
  },
);

if (stream) {
  for await (const row of stream) {
    await warehouse.insert(row);
  }
}
For smaller datasets: await soundlink.metrics.breakdown.export.collect('camp_abc123'). Engagement exports work the same way: soundlink.metrics.engagement.export and .export.collect. See Understanding metrics and JSONL exports.

Configuration

const soundlink = new Soundlink({
  apiKey: process.env.SOUNDLINK_API_KEY!,
  baseUrl: "https://api.getsoundlink.com", // default
  timeout: 30_000,
  maxRetries: 2,
  fetch: customFetch, // optional — tests or Edge
});

Methods

Every method maps 1:1 to a Public API route. All return Promise<ApiResponse<T>> unless noted.

System

MethodREST endpointReturns
soundlink.ping()GET /v1/pingApiResponse<PingData>

Campaigns

Requires campaigns:read.
MethodREST endpointReturns
soundlink.campaigns.list(params?)GET /v1/campaignsApiResponse<CampaignListData>
soundlink.campaigns.get(campaignId)GET /v1/campaigns/{id}ApiResponse<CampaignDetail>
soundlink.campaigns.listAll(params?)Paginated listAsyncGenerator<CampaignSummary>
list params: page, pageSize (max 100), sortBy (createdAt | status), sortOrder (asc | desc).

Metrics

Requires metrics:read.
MethodREST endpointReturns
soundlink.metrics.overview(id, params?)GET .../metrics/overviewApiResponse<MetricsOverview>
soundlink.metrics.breakdown.list(id, params?)GET .../metrics/breakdownApiResponse<BreakdownListData>
soundlink.metrics.breakdown.export(id, params?)GET .../metrics/breakdown/exportApiResponse<JsonlStream<BreakdownRow>>
soundlink.metrics.breakdown.export.collect(id, params?)Same export routeApiResponse<ExportCollection<BreakdownRow>>
soundlink.metrics.engagement.list(id, params?)GET .../metrics/engagementApiResponse<EngagementListData>
soundlink.metrics.engagement.export(id, params?)GET .../metrics/engagement/exportApiResponse<JsonlStream<EngagementRow>>
soundlink.metrics.engagement.export.collect(id, params?)Same export routeApiResponse<ExportCollection<EngagementRow>>
Date params use inclusive YYYY-MM-DD. Export windows are capped at 90 days per request. List pageSize max is 500.
metrics.engagement.list is spec-ready in the SDK; the backend route may return 404 until it ships.

Types

All public types are exported from soundlink for use in your app:
import type {
  ApiResponse,
  ApiError,
  ApiMeta,
  PingData,
  CampaignSummary,
  CampaignDetail,
  CampaignListData,
  CampaignListParams,
  MetricsOverview,
  BreakdownRow,
  BreakdownListData,
  BreakdownListParams,
  EngagementRow,
  EngagementListData,
  EngagementListParams,
  ExportCollection,
  JsonlStream,
  SoundlinkClientOptions,
  PublicApiErrorCode,
} from "soundlink";
GroupTypes
EnvelopeApiResponse<T>, ApiError, ApiMeta, PublicApiErrorCode
CampaignsCampaignSummary, CampaignDetail, CampaignListData, CampaignListParams, CampaignStatus
MetricsMetricsOverview, BreakdownRow, BreakdownListData, EngagementRow, EngagementListData
ParamsDateRangeParams, BreakdownListParams, EngagementListParams, BreakdownExportParams, EngagementExportParams
ExportsJsonlStream<T>, ExportCollection<T>
ClientSoundlink, SoundlinkClientOptions
Row shapes match the warehouse schemas documented in Understanding metrics. For full field lists, use the API Reference or your IDE hover hints on the imported types.

Errors

The SDK has two error paths. Most of the time you only need the first.

API errors (returned, not thrown)

When the Public API returns 4xx or 5xx, the SDK resolves with { data: null, error, meta? }:
const { data, error, meta } = await soundlink.campaigns.get("camp_unknown");

if (error) {
  // error.code    — e.g. 'campaign_not_found'
  // error.message — human-readable
  // error.status  — HTTP status (404, 429, …)
  // error.requestId — same as meta?.requestId
  // error.retryAfter — seconds, on 429 only

  if (error.code === "rate_limit_exceeded" && error.retryAfter) {
    await sleep(error.retryAfter * 1000);
  }
}
Always check error before using data. Error codes and HTTP mapping: Errors.

SDK errors (thrown)

These extend SoundlinkSdkError and mean something went wrong outside a normal API response:
ClassWhen
SoundlinkConfigErrorMissing or invalid API key at construction
SoundlinkParseErrorResponse body could not be parsed
SoundlinkSdkErrorUnexpected transport failure
import { Soundlink, SoundlinkConfigError } from "soundlink";

try {
  const soundlink = new Soundlink({ apiKey: "" }); // throws SoundlinkConfigError
} catch (err) {
  if (err instanceof SoundlinkConfigError) {
    console.error("Fix your API key configuration");
  }
}
Use try/catch for client setup and rare transport failures. Use { data, error } for everything the API returns.

Next

Syncing campaignsUnderstanding metricsJSONL exports