npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@interhumanai/sdk

v0.15.0

Published

Official TypeScript SDK for the Interhuman API (auth, upload analysis, live stream analysis, and realtime multi-track analysis).

Readme

Interhuman TypeScript SDK

Official TypeScript/JavaScript SDK for the Interhuman API. It wraps the public API surfaces behind a small, typed client:

  • Auth — exchange API-key credentials for a short-lived access token (POST /v1/auth), or mint/revoke capped client tokens for direct browser use (POST /v1/client_tokens).
  • Upload — analyze a complete video file (POST /v1/upload/analyze).
  • Stream — analyze a live feed over a WebSocket (WS /v1/stream/analyze, or WS /v2/stream/analyze for the Inter-2 model).
  • Realtime — analyze a live feed with multi-track analysis and periodic recommendations (WS /v0/realtime/analyze).

The SDK handles the token exchange, multipart upload, and the WebSocket envelope protocol for you, so you give it your API key once and make a single call per surface.

This SDK targets the API exactly as it ships today. The realtime endpoint ships under the /v0 path, which may change when it graduates to v1, and requires the interhumanai.realtime scope.

Installation

npm install @interhumanai/sdk

The package ships both ESM and CommonJS builds with TypeScript declarations.

Supported runtimes

| Surface | Requirement | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Auth, Upload | A global fetch, FormData, and BlobNode.js ≥ 18 or any modern browser. On older Node, pass a fetch implementation via the fetch option. | | Stream, Realtime | A WebSocket. Node.js ≥ 22 (global WebSocket) or any modern browser. On older Node, pass a webSocket factory (e.g. backed by the ws package). |

The SDK has no runtime dependencies.

Quickstart

import { InterhumanClient, IncludeFlag } from "@interhumanai/sdk";

const client = new InterhumanClient({
  credentials: {
    keyId: process.env.INTERHUMAN_KEY_ID!,
    keySecret: process.env.INTERHUMAN_KEY_SECRET!,
  },
  // environment: "staging", // defaults to "production"
});

// Upload a finished file
const report = await client.upload.analyze({
  file: { data: bytes, filename: "clip.mp4", contentType: "video/mp4" },
  include: [IncludeFlag.ConversationQualityOverall],
});
console.log(report.signals, report.conversation_quality?.overall);

InterhumanClient exchanges your credentials at /v1/auth and refreshes the resulting token automatically before it expires. If you already have a bearer token — including a direct API key, which the API accepts as a bearer token on every request — pass accessToken instead of credentials:

const client = new InterhumanClient({ accessToken: process.env.IH_API_KEY! });

Authentication

Get an API key (keyId + keySecret) from the Interhuman customer platform. You can let the client manage tokens for you (recommended), or mint one yourself:

import { AuthClient, Scope } from "@interhumanai/sdk";

const auth = new AuthClient();
const token = await auth.createToken({
  keyId,
  keySecret,
  scopes: [Scope.Upload, Scope.Stream],
});
// token.access_token, token.expires_in (≈900s), token.scope

Scopes:

  • Scope.Upload (interhumanai.upload) — required for upload.
  • Scope.Stream (interhumanai.stream) — required for stream.
  • Scope.Realtime (interhumanai.realtime) — required for realtime.

Each scope grants only its own endpoint, so request every scope the client needs — holding one never implies another.

Client tokens (browser use)

To let a browser call the upload, stream, or realtime API without shipping your API key, mint a short-lived, capped client token on your backend and hand only that token to the browser:

const token = await auth.createClientToken({
  apiKey: process.env.INTERHUMAN_API_KEY!,
  scopes: [Scope.Stream], // optional; defaults to stream
  expiresIn: 300, // optional; clamped to 60–3600s
  maxConcurrent: 1, // one session at a time (the default)
  maxVideoSeconds: 600, // total video budget across upload/stream/realtime
  allowedOrigins: ["https://app.example.com"],
});
// token.access_token — safe to hand to the browser; the API enforces the caps.

The token grants the requested scopes (default stream), lives 60–3600s, and carries per-token caps (duration, video bytes, concurrency, video-second budget, origin allow-list) that the API enforces across upload, stream, and realtime (the video budget spans all three). Cut a token off early by revoking it — after which it is rejected on new requests and any live session it opened is closed:

await auth.revokeClientToken({
  apiKey: process.env.INTERHUMAN_API_KEY!,
  token: token.access_token,
});

There is no "extend a token" call — mint a new one when a token nears expiry or its video budget runs low.

Upload API

const report = await client.upload.analyze({
  file: { data: bytes, filename: "clip.mp4", contentType: "video/mp4" },
  include: ["conversation_quality_overall", "conversation_quality_timeline"],
  goalDimensions: ["clarity", "energy"], // triggers interaction feedback
  // conversationContext: "Sales discovery call", // alternative feedback driver
});
  • file accepts a Blob/File or { data, filename?, contentType? } where data is a Uint8Array, ArrayBuffer, or Blob.
  • Formats: mp4, avi, mov, mkv, mpeg-ts, webm. The clip must be ≥ 3 seconds and the upload ≤ 32 MB.
  • The response is an AnalysisResult with signals, engagement_state, and optional conversation_quality / feedback.
  • Every Signal carries a modality naming the analysis modalities that detected it. When several tracks detect the same signal, every contributing modality is included, e.g. ["audio", "visual"].

Stream API

const stream = client.stream();

stream.on("signal.detected", (e) => console.log(e.data.signal_type, e.data.start));
stream.on("signal.updated", (e) => console.log(e.data.signal_type, e.data.probability));
stream.on("signal.ended", (e) => console.log(e.data.signal_type, e.data.end));
stream.on("engagement.updated", (e) => console.log(e.data.state));
stream.on("conversation_quality.updated", (e) => console.log(e.data.overall));
stream.on("coverage.dropped", (e) => console.warn(e.data.ranges));
stream.on("coverage.degraded", (e) => console.warn(e.data.ranges, e.data.reason));
stream.on("error", (e) => console.error(e.data.code, e.data.message));
stream.on("session.ended", (e) => console.log("analysis closed:", e.data.reason));
stream.on("close", (info) => console.log(info.code, info.reason));

await stream.connect();
await stream.waitForSessionReady();

// Optional per-session settings; the last config sent wins.
stream.updateConfig({ include: ["conversation_quality_overall"], goal_dimensions: ["clarity"] });

// Send WebM or fragmented-MP4 video chunks as they are recorded (each ≤ 32 MB).
stream.sendVideo(chunk); // Uint8Array | ArrayBuffer | Blob

// Graceful shutdown: the server drains the accepted video, ends still-active
// signals, sends session.ended, and closes the socket itself (code 1000).
stream.requestClose();
  • Authentication uses the Sec-WebSocket-Protocol: access_token, <token> subprotocol pair. This is the portable method that works in browsers (where the standard WebSocket constructor cannot set an Authorization header) and in Node.
  • Server→client frames are typed envelopes { type, timestamp, correlation_id, data }. on(type, …) is fully typed per event; on("message", …) receives every envelope as the StreamEvent union.
  • Client→server: video is sent as binary frames; session config as a JSON text frame.
  • session.ready (available via waitForSessionReady()) reports the session's limits: idle timeout (5 min default), max duration (1 hour default), and the min/max chunk size. waitForSessionReady() rejects with an InterhumanError if the connection closes before the session becomes ready — for example when the server accepts the handshake, sends an error envelope, and closes — so awaiting it never hangs on a refused session.
  • requestClose() starts the graceful shutdown handshake: the server replies with session.closing (its max_drain_seconds is the longest you should wait), rejects any further video, finishes analyzing what it already accepted, emits signal.ended for still-active signals, sends session.ended, and closes the socket. close() remains the immediate, non-draining teardown.

Choosing the model: v1 and v2

client.stream() opens WS /v1/stream/analyze, analyzed by the Inter-1 model. Pass { apiVersion: "v2" } to open WS /v2/stream/analyze and have the same session analyzed by the Inter-2 model:

const stream = client.stream({ apiVersion: "v2" });

Everything else is identical — the scope, the session.ready limits, session config, the video you send, the events you receive and their order, and the graceful close — so the handlers and types above work unchanged. The one difference is how the client names itself: errors raised for a v2 session say "Inter-2 stream" where a v1 session says "Stream", so a message names the endpoint the session actually opened. If a deployment has no Inter-2 backend configured, a v2 session is refused right after the handshake with an error envelope (ih1003) and close code 1013.

Limitations

  • The stream protocol accepts WebM and fragmented MP4 chunks — the two shapes browsers produce via MediaRecorder (video/webm on most browsers, video/mp4 on Safari). A server-side pipeline must encode to one of the two. Clips cut at arbitrary byte offsets mid-structure are only supported for WebM.
  • coverage.dropped is informational, not an error: under heavy load some media is skipped (and not billed) while the session continues.
  • coverage.degraded is likewise informational: the listed ranges were analyzed (with their audio) and are billed normally, but their windows decoded materially less video than they span — typically a screen share or other long-GOP stream whose keyframes are sparser than the analysis window.
  • An abruptly disconnected session does not emit a trailing signal.ended for still-active signals — treat close as ending them at the last emitted time. End the session with requestClose() to receive them explicitly.

Realtime API

The Realtime API is a sibling of the Stream API: same ingest (send video chunks, receive signal.* events), plus multi-track analysis, a transcript channel in both directions, and periodic written guidance (recommendations). client.realtime() mirrors client.stream():

const realtime = client.realtime();

realtime.on("signal.detected", (e) => console.log(e.data.signal_type, e.data.start));
realtime.on("realtime_recommendation.generated", (e) => console.log("guidance:", e.data.text));
realtime.on("error", (e) => console.error(e.data.code, e.data.message));

await realtime.connect();
const ready = await realtime.waitForSessionReady();
console.log(ready.data.supported_session_config_options.analysis_groups);

// Recommendations are opt-in: the instructions are the switch that enables them.
realtime.updateConfig({
  analysis_groups: ["audio", "visual"], // subset of what session.ready advertises
  realtime_recommendation_instructions: "Goal: help me close a sales call.",
  realtime_recommendation_frequency: "medium", // high|medium|low = every 10/20/30s of analyzed video
});

// Optionally supply your own transcript; it feeds the recommendation prompt.
realtime.sendTranscript([{ start: 0.4, end: 2.3, text: "Yeah, exactly.", speaker: 0 }]);

realtime.sendVideo(chunk); // same binary chunks as the stream client

// Graceful shutdown, same handshake as the stream client: the server drains
// the accepted video and any in-flight recommendations, sends session.ended, and
// closes the socket itself (code 1000).
realtime.requestClose();

Notes on the realtime client:

  • The session config takes analysis_groups, realtime_recommendation_instructions, and realtime_recommendation_frequency. The instructions shape the guidance's goal, domain, and tone.
  • realtime_recommendation.generated carries the periodic guidance.
  • The visual analysis group reports its negative signal as "tension" and the audio group reports "frustration". They describe the same state seen versus heard, and both can be active at once, each with its own lifecycle, so code that branches on signal_type should handle both.
  • sendTranscript(segments) sends a transcript.updated frame; the most recent transcript replaces any prior one. Text frames — config, transcript, session.close — remain accepted while a graceful close drains, and a transcript sent during the drain still feeds recommendation runs started by draining windows.
  • Scope and path: the route is WS /v0/realtime/analyze (not v1) and requires tokens holding Scope.Realtime, so include it in the scopes the client requests alongside the ones you still use — a token grants exactly what was requested, so scopes: [Scope.Realtime] alone would close off upload() and stream(). Expect the path to change when the endpoint graduates to v1.

Errors

  • HTTP failures throw InterhumanApiError carrying status, errorId (e.g. "ih2001"), correlationId, link, and the raw body.
  • Misconfiguration throws InterhumanConfigError before any network call.
  • Stream error envelopes arrive on the "error" event; transport-level socket failures arrive on "socketError".

See the error handling reference.

SDK attribution

Every request the SDK makes tells the API which SDK sent it, so Interhuman can report SDK adoption and spot clients stuck on an old release:

  • HTTP requests carry X-Interhuman-SDK: typescript/<version>.
  • Stream and realtime WebSocket handshakes carry ih_sdk=typescript&ih_sdk_version=<version> in the URL, because a browser WebSocket cannot set custom handshake headers. The Sec-WebSocket-Protocol: access_token, <credential> authentication contract is unchanged, and any query parameters your baseUrl already had are kept. (Releases before 0.15.0 sent the same values as sdk/sdk_version; the API accepts both spellings.)

The version is read from the package's own package.json, so it always matches the release you installed. Nothing else is sent — no device, OS, runtime, hostname, application name, or end-user identifier — and because any caller can send the same values, the API treats the metadata as a self-declared hint that never affects authentication, authorization, quotas, or billing. Servers that predate it ignore it.

SDK_NAME, SDK_VERSION, SDK_HEADER_NAME, and sdkHeaderValue() are exported if you want to inspect exactly what is sent.

Environments

new InterhumanClient({ credentials, environment: "production" }); // api.interhuman.ai (default)
new InterhumanClient({ credentials, environment: "staging" }); // staging-api.interhuman.ai
new InterhumanClient({ credentials, baseUrl: "http://localhost:8080" }); // local dev

Examples

Runnable examples live in examples/: auth.ts, client-tokens.ts, upload.ts, stream.ts, realtime.ts.

Development

npm install
npm run typecheck
npm test
npm run build      # emits dist/ (ESM + CJS + .d.ts)

The build and test toolchain needs Node.js ≥ 20.19 or ≥ 22.12 (Vite's floor); CI runs Node 22. That is a contributor prerequisite only — the published package still supports the runtimes in Supported runtimes above.

The unit tests live in the repository's top-level test tree at tests/sdk/typescript/ (mirroring this package's path), while the runner config stays here — run them with npm test from this directory.

API reference docs

The reference documentation on docs.interhuman.ai is generated from this package's TSDoc comments — do not hand-write it. Edit the comments in src/, then regenerate:

npm run docs   # TypeDoc → Mintlify MDX in docs-dist/mintlify/

On every push to main that touches the SDK, the Sync Docs to interhuman-docs workflow regenerates this page and opens a PR against the interhuman-docs repository. That workflow keeps a single rolling docs-sync PR open — a later run updates it in place instead of opening another one.

Releasing

This package is versioned independently from the API service using semantic versioning, but in lockstep with the Python SDK (interhumanai on PyPI): both always carry the same version and release together from a single workflow. Publishing is automatic on a version bump, gated on a successful deploy:

  1. Bump version in package.json and __version__ in sdk/python/src/interhumanai/_version.py to the same value, and add a CHANGELOG.md entry to each package.
  2. Merge to main.
  3. After the Deploy workflow succeeds, the SDK release workflow builds, tests, and publishes both packages. It is idempotent per registry — it publishes only when the version changes — and tags the release sdk-v<version>.

You can also publish manually by pushing an sdk-vX.Y.Z tag (must match both SDK versions) or via workflow_dispatch (with an optional dry run). The sdk-v* tag namespace is intentionally separate from the API service's automatic release tags so the two never collide.

Setup, the NPM_TOKEN secret, and token rotation (our policy re-mints the npm token about every 90 days) are documented in docs/sdk-release.md.

License

Apache-2.0