@interhumanai/sdk
v0.6.0
Published
Official TypeScript SDK for the Interhuman API (auth, upload analysis, live stream analysis, and real-time multi-track analysis).
Maintainers
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). - Real-Time — analyze a live feed with multi-track analysis and periodic
feedback (
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 real-time endpoint is a temporary v0 release: it lives under the
/v0path and accepts either the stream or the realtime scope. Both aspects may change when it graduates to v1.
Installation
npm install @interhumanai/sdkThe package ships both ESM and CommonJS builds with TypeScript declarations.
Supported runtimes
| Surface | Requirement |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Auth, Upload | A global fetch, FormData, and Blob — Node.js ≥ 18 or any modern browser. On older Node, pass a fetch implementation via the fetch option. |
| Stream, Real-Time | 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.scopeScopes:
Scope.Upload(interhumanai.upload) — required for upload.Scope.Stream(interhumanai.stream) — required for stream. For the current temporary real-time release it also grants access to real-time.Scope.Realtime(interhumanai.realtime) — the dedicated real-time scope (accepted by real-time alongsideScope.Streamfor this release).
Client tokens (browser use)
To let a browser call the upload, stream, or real-time 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/real-time
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 real-time (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
});fileaccepts aBlob/Fileor{ data, filename?, contentType? }wheredatais aUint8Array,ArrayBuffer, orBlob.- Formats: mp4, avi, mov, mkv, mpeg-ts, webm. The clip must be ≥ 3 seconds and the upload ≤ 32 MB.
- The response is an
AnalysisResultwithsignals,engagement_state, and optionalconversation_quality/feedback.
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("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 standardWebSocketconstructor cannot set anAuthorizationheader) 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 theStreamEventunion. - Client→server: video is sent as binary frames; session config as a JSON text frame.
session.ready(available viawaitForSessionReady()) reports the session's limits: idle timeout (5 min default), max duration (1 hour default), and the min/max chunk size.requestClose()starts the graceful shutdown handshake: the server replies withsession.closing(itsmax_drain_secondsis the longest you should wait), rejects any further video, finishes analyzing what it already accepted, emitssignal.endedfor still-active signals, sendssession.ended, and closes the socket.close()remains the immediate, non-draining teardown.
Limitations
- The stream protocol accepts WebM and fragmented MP4 chunks — the two
shapes browsers produce via
MediaRecorder(video/webmon most browsers,video/mp4on 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.droppedis informational, not an error: under heavy load some media is skipped (and not billed) while the session continues.- An abruptly disconnected session does not emit a trailing
signal.endedfor still-active signals — treatcloseas ending them at the last emitted time. End the session withrequestClose()to receive them explicitly.
Real-Time API
The Real-Time 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 (feedback). 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("transcript.generated", (e) => console.log("live transcript:", e.data.text));
realtime.on("realtime_feedback.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);
// Feedback is opt-in: the instructions are the switch that enables it.
realtime.updateConfig({
analysis_groups: ["audio", "visual"], // subset of what session.ready advertises
realtime_feedback_instructions: "Goal: help me close a sales call.",
realtime_feedback_frequency: "medium", // high|medium|low = every 10/20/30s of analyzed video
});
// Optionally supply your own transcript; it feeds the feedback 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 feedback, sends session.ended, and
// closes the socket itself (code 1000).
realtime.requestClose();Differences from the stream client:
- The session config takes
analysis_groups/realtime_feedback_instructions/realtime_feedback_frequency— not the streamincludeflags — and the endpoint never emitsengagement.updatedorconversation_quality.updated. - Two extra server events:
transcript.generated(the server's own live, de-overlapped transcript — concatenate thetextfields to reconstruct it) andrealtime_feedback.generated(the periodic guidance, orNO_GUIDANCE). signal.*events do not require thevideoanalysis group.sendTranscript(segments)sends atranscript.updatedframe; 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 feedback runs started by draining windows.- Temporary release: the route is
WS /v0/realtime/analyze(not v1) and accepts tokens holding eitherScope.StreamorScope.Realtime. Expect the path and accepted scopes to change when the endpoint graduates to v1. The previous hyphenated spellingWS /v0/real-time/analyzestill works as a temporary compatibility alias; new integrations should useWS /v0/realtime/analyze.
Errors
- HTTP failures throw
InterhumanApiErrorcarryingstatus,errorId(e.g."ih2001"),correlationId,link, and the rawbody. - Misconfiguration throws
InterhumanConfigErrorbefore any network call. - Stream
errorenvelopes arrive on the"error"event; transport-level socket failures arrive on"socketError".
See the error handling reference.
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 devExamples
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 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 SDK docs to Docs
workflow regenerates this page and opens a PR against the interhuman-docs
repository.
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:
- Bump
versioninpackage.jsonand__version__insdk/python/src/interhumanai/_version.pyto the same value, and add aCHANGELOG.mdentry to each package. - Merge to
main. - After the
Deployworkflow succeeds, theSDK releaseworkflow builds, tests, and publishes both packages. It is idempotent per registry — it publishes only when the version changes — and tags the releasesdk-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.
