Skip to main content

@digits/clinical-sdk

Official TypeScript SDK for the Digits Health Clinical API — the Fastify service (apps/api) that powers hand-rehabilitation assessments, patient records, biomechanics analysis, reports, and billing.

It is a thin, fully-typed wrapper over the REST API: one DigitsClient with a resource per route group, automatic bearer-token injection, request timeouts, retry-with-backoff for idempotent calls, and typed errors.

  • Zero runtime dependencies — uses the platform fetch (Node 18+, browsers, edge).
  • Isomorphic — runs in Node, the browser, and edge runtimes.
  • Typed end-to-end — request/response types mirror the API and @digits/shared.

Full endpoint reference, authentication details, and the OpenAPI spec live in apps/api/docs/.


Installation

Inside this monorepo it is available as a workspace package:

// package.json
{
  "dependencies": {
    "@digits/clinical-sdk": "*"
  }
}
npm install

Node < 18 must supply a fetch implementation (see Custom fetch).


Quick start

import { DigitsClient } from '@digits/clinical-sdk';

const digits = new DigitsClient({
  baseUrl: 'https://api.digitshealth.com/api', // note: include the /api prefix
  token: () => getAccessToken(),               // string | () => string | Promise<string>
});

// Health check (no auth)
const health = await digits.health.check();

// Submit a Range-of-Motion assessment
const rom = await digits.assessments.submitRom({
  hand: 'RIGHT',
  overallScore: 82,
  wristScore: 78,
  fingerScore: 86,
  wrist: { max_extension_deg: 58, max_flexion_deg: 64, unsigned_rom_deg: 122 },
  joints: [],
});

console.log(rom.formattedCode); // e.g. "XHE-7JR" — shareable code

// Retrieve results later by code (public)
const results = await digits.assessments.getResults(rom.code);

Configuration

new DigitsClient({
  baseUrl: 'https://api.digitshealth.com/api', // required
  token: 'eyJ...',          // optional bearer token or () => string | Promise<string>
  timeoutMs: 30_000,        // optional, per-request timeout (default 30s)
  maxRetries: 2,            // optional, retries for idempotent calls on 5xx/network (default 2)
  headers: { 'X-Trace': '1' }, // optional, attached to every request
  userAgent: 'my-app/1.0',  // optional
  fetch: customFetch,       // optional, override the fetch implementation
});

Base URL

The API mounts every route under /api, so baseUrl should end with /api. Common values:

EnvironmentBase URL
Local devhttp://localhost:3001/api
Dev/Cloudhttps://dev.digitshealth.com/api
Productionhttps://api.digitshealth.com/api

Authentication

Most write paths use optional auth (they work anonymously, but link to the caller when a token is present). Patient and billing endpoints require a valid Auth0 RS256 JWT. Provide it via token:

// Static token
new DigitsClient({ baseUrl, token: accessToken });

// Lazy/refreshing token — resolved on every request
new DigitsClient({ baseUrl, token: async () => (await auth.getAccessToken()).token });

See apps/api/docs/authentication.md.

Custom fetch

import { DigitsClient } from '@digits/clinical-sdk';
import fetch from 'node-fetch'; // Node < 18

const digits = new DigitsClient({ baseUrl, fetch: fetch as unknown as typeof globalThis.fetch });

Resources

AccessorEndpointsAuth
digits.healthcheck()none
digits.assessmentssubmitRom, submitDexterity, submitPain, submitArthritis, submitLegacyPain, getResultsoptional
digits.lookupbyCode, claim, email, createAnonymousmixed
digits.patientsmyScores, myAssessments, list, get, assessments, journal, scores, narrativeSummaryrequired
digits.journalcreate, list, get, narrativeContextnone*
digits.reportscreate, get, downloadPdf, email, explainoptional
digits.billingcheckout, verifyCheckout, portal, subscriptionrequired
digits.biomechanicswrist*, thumb*none
digits.aittsnone
digits.leadsdevelopernone

* Journal endpoints currently have no auth guard but expect a patientId.


Error handling

Every non-2xx response throws a DigitsApiError; network/timeout failures throw a DigitsConnectionError. Both extend DigitsError.

import { DigitsApiError, DigitsConnectionError } from '@digits/clinical-sdk';

try {
  await digits.billing.checkout('plus');
} catch (err) {
  if (err instanceof DigitsApiError) {
    console.error(err.status, err.code, err.message);
    if (err.isAuthError) redirectToLogin();
    if (err.code === 'ALREADY_SUBSCRIBED') showUpgradeNotice();
  } else if (err instanceof DigitsConnectionError) {
    console.error('Network problem:', err.message);
  }
}

DigitsApiError exposes status, code?, body, request, and the helpers isAuthError, isNotFound, isServerError.


Retries & idempotency

GET requests (and explicitly idempotent calls) are retried up to maxRetries times on 5xx responses and connection errors, using exponential backoff with jitter. POST calls are not retried by default to avoid duplicate writes.


Examples

Provider: list patients needing attention

const { patients } = await digits.patients.list({
  attention: true,
  trend: 'declining',
  sort: 'score',
  limit: 25,
});

Bundle assessments into a report and download the PDF

const report = await digits.reports.create({
  title: 'Q2 Hand Therapy Progress',
  assessmentCodes: ['XHE7JR', 'K9M2PQ'],
});

await digits.reports.explain(report.code); // generate AI narrative
const pdf = await digits.reports.downloadPdf(report.code); // ArrayBuffer

Anonymous flow + claim later

const anon = new DigitsClient({ baseUrl });
const created = await anon.lookup.createAnonymous({ assessmentType: 'ROM', hand: 'RIGHT' });

// ...after the user logs in:
const authed = new DigitsClient({ baseUrl, token: accessToken });
await authed.lookup.claim(created.code);

Escape hatch

Endpoints not yet wrapped can be called directly via the low-level transport, keeping auth, retries, and error handling:

const data = await digits.http.request<MyType>({
  method: 'GET',
  path: '/some/new/endpoint',
  query: { foo: 'bar' },
});

Development

npm -w @digits/clinical-sdk run test       # vitest
npm -w @digits/clinical-sdk run typecheck  # tsc --noEmit
npm -w @digits/clinical-sdk run build      # emit dist/

Versioning

This SDK tracks the Clinical API contract. The API has no codegen step — when a route DTO changes in apps/api or packages/shared, update the matching type in src/types.ts and the resource method here. See CHANGELOG.md.