@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:
| Environment | Base URL |
|---|---|
| Local dev | http://localhost:3001/api |
| Dev/Cloud | https://dev.digitshealth.com/api |
| Production | https://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
| Accessor | Endpoints | Auth |
|---|---|---|
digits.health | check() | none |
digits.assessments | submitRom, submitDexterity, submitPain, submitArthritis, submitLegacyPain, getResults | optional |
digits.lookup | byCode, claim, email, createAnonymous | mixed |
digits.patients | myScores, myAssessments, list, get, assessments, journal, scores, narrativeSummary | required |
digits.journal | create, list, get, narrativeContext | none* |
digits.reports | create, get, downloadPdf, email, explain | optional |
digits.billing | checkout, verifyCheckout, portal, subscription | required |
digits.biomechanics | wrist*, thumb* | none |
digits.ai | tts | none |
digits.leads | developer | none |
* 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.