Getting started
This guide gets you from zero to a successful API call.
Base URLs
Every route is mounted under the /api prefix. Your base URL must include /api.
| Environment | Base URL | Notes |
|---|---|---|
| Local dev | http://localhost:3001/api | npm run dev from repo root (or apps/api). |
| Dev / Cloud Run | https://dev.digitshealth.com/api | Shared development deployment. |
| Production | https://api.digitshealth.com/api | Live patient data. |
The web app does not talk to these origins directly from the browser. It uses a same-origin
BFF proxy at /api/proxy/<path> that attaches the Auth0 token server-side. Native and
server-to-server integrations call the API origin directly with their own bearer token.
Running locally
From the repo root (Node 22, npm 11):
npm install
npm run dev # turbo: web on :3002, api on :3001
Or just the API:
cd apps/api
cp .env.example .env # set DATABASE_URL at minimum
npm run dev # prisma migrate deploy + generate, then tsx watch
When AUTH0_DOMAIN is unset, the API runs in dev mode: it injects a mock user
(sub: 'dev|1', roles ['patient', 'care_provider']) so every authenticated route works
without a real token. See Authentication.
Confirm it's up:
curl http://localhost:3001/api/health
# {"status":"ok","timestamp":"2026-06-07T...","version":"0.1.0"}
Your first real call
With the SDK (recommended)
import { DigitsClient } from '@digits/clinical-sdk';
const digits = new DigitsClient({ baseUrl: 'http://localhost:3001/api' });
const result = 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(result.formattedCode); // "XHE-7JR"
With curl
curl -X POST http://localhost:3001/api/assessments/rom \
-H 'Content-Type: application/json' \
-d '{
"hand": "RIGHT",
"overallScore": 82,
"wristScore": 78,
"fingerScore": 86,
"wrist": { "max_extension_deg": 58, "max_flexion_deg": 64, "unsigned_rom_deg": 122 },
"joints": []
}'
Response (201 Created):
{ "id": 1, "code": "XHE7JR", "formattedCode": "XHE-7JR", "overallScore": 82, "wristScore": 78, "fingerScore": 86 }
Fetch it back by code (public, no auth):
curl http://localhost:3001/api/assessments/XHE7JR/results
Authenticated call
curl http://localhost:3001/api/patients/me/scores \
-H "Authorization: Bearer $ACCESS_TOKEN"
const digits = new DigitsClient({
baseUrl: 'https://api.digitshealth.com/api',
token: () => getAccessToken(), // refreshed per request
});
const { scores } = await digits.patients.myScores({ hand: 'RIGHT' });
Conventions you should know
- Content type: send and expect
application/json. PDFs and TTS audio are the exceptions (see the API reference). - IDs: the database uses BigInt primary keys; responses serialize them as plain JSON numbers.
- Assessment codes: six characters from the alphabet
ABCDEFGHJKMNPQRSTUVWXYZ23456789(no0/O/1/I/L). Accepted with or without the middle dash. Anonymous records expire after 90 days; claimed records never expire. - Pagination: list endpoints take
limitandoffset; each documents its cap. - CORS: the API allows the configured
FRONTEND_URL, localhost (3000/3002), and the Cloud Run web origins. Add new browser origins inapps/api/src/server.ts.
Next steps
- Authentication — get and use a token.
- API reference — the full surface area.
- Clinical concepts — what the scores and enums mean.