← Developers

TypeScript SDK

@hitsonar/sdk: full types generated from the live OpenAPI document, typed error classes, timeout handling, and rate-limit/usage-limit awareness, on top of nothing but fetch.

Publish-ready, not yet available from the public registry. @hitsonar/sdk has not been published to npm, so npm install @hitsonar/sdk will not resolve. Until it is, use the single-file TypeScript client — the same request shapes and error handling, with no dependency — or build from packages/sdk-ts in the source repository. This page is published now so the interface can be reviewed before it is frozen; the samples below are accurate against that source.

Install

For reference. This command will work once the package is published; it does not yet.

npm install @hitsonar/sdk

Quickstart

import { HitSonar } from "@hitsonar/sdk";

const client = new HitSonar({ apiKey: process.env.HITSONAR_API_KEY! });

const result = await client.analyze("how to start a podcast");
console.log(result.score.overall, result.score.grade);

HitSonar holds a secret key. Construct it on a server, never in code a browser runs — see Where a secret key belongs.

Titles from a title

generateTitles() is the whole title workflow: HitSonar derives the research topics, measures them, rejects the search angles whose first page belongs to a different kind of video, reads how the titles ranking there are built, scores the title you sent and writes scored alternatives. It is the slowest call in this client, so it uses its own 150-second timeout rather than the constructor’s default.

// A title is the whole request. No keyword, no search volume, no brief.
const result = await client.generateTitles({
  title: "No Condemnation - Putting Down the Shame You Were Never Meant to Carry",
});

// TWO different scores. Never show both as "score".
result.research.selected?.opportunityScore;  // HitSonar Score: is the TOPIC worth pursuing
result.recommended?.titleScore.overall;      // Title Opportunity: how well the TITLE is built

// Recommendations with no research behind them must not look like ones with it.
if (result.research.status === "no_suitable_topic") {
  // Every candidate topic was rejected on search intent, or had no analysis.
  // The titles are still real; they rest on the brief alone.
}

// The rejections are the useful part.
for (const c of result.research.considered) {
  if (c.decision === "rejected") console.log(c.phrase, c.reason);
}

// Counted from the selected page's real titles. Prevalence, never a ranking claim.
result.titleStyle?.observations; // ["6 of 10 titles use a colon.", ...]

It costs one title generation from a separate monthly allowance, plus up to three new analyses — research.analysesRun reports how many it actually spent. Full field reference: POST /v2/titles.

Errors

Every non-2xx response throws a subclass of HitSonarError, matching the API’s error.code:

import { HitSonar, RateLimitError, UsageLimitError, AuthenticationError, InsufficientContextError } from "@hitsonar/sdk";

try {
  await client.analyze(phrase);
} catch (e) {
  if (e instanceof RateLimitError) {
    // e.retryAfterSeconds
  } else if (e instanceof UsageLimitError) {
    // e.usage: { used, limit, resetsAt }
  } else if (e instanceof AuthenticationError) {
    // bad or missing key
  } else if (e instanceof InsufficientContextError) {
    // generateTitles() only: the title names an occasion, not a subject ("Episode 11").
    // Retrying will not help -- send a description, or a better working title.
    // It is a subclass of InvalidRequestError, so older catch blocks still see it.
  }
  // every HitSonarError carries .code, .message, .param, .requestId, .status
}

A request that never reaches the server (a timeout, or the network) throws HitSonarNetworkError, not a HitSonarError — there is no response envelope to read a code from.

Rate limits and usage

client.lastRequestId, client.lastRateLimit, and client.lastUsage are updated after every call, success or failure, from the response headers. Quote lastRequestId when contacting support about a specific call.

Options

new HitSonar({
  apiKey: "hs_...",
  baseUrl: "https://app.hitsonar.com", // override for self-hosted or staging
  timeoutMs: 60_000,                    // a cold /v2/analyze run can take ~20s
                                        // generateTitles() uses its own 150s default instead
  fetch: customFetch,                   // inject your own fetch, e.g. for testing
});

Versioning

This package’s version is independent of API_VERSION and score.version. It follows ordinary semver for its own surface: a patch for a bug fix, a minor version for a backward-compatible addition, a major version only when existing code would need to change. See the versioning and deprecation policy.

Source

Hand-written on top of types generated by openapi-typescript from the live spec, in the main HitSonar repository at packages/sdk-ts. Full install/build/test/publish instructions are in that package’s README.md.