Errors
Error envelope
Failed requests return a JSON object with an error message, and occasionally a
machine-readable code:
{ "error": "hand is required" }
{ "error": "Already subscribed", "code": "ALREADY_SUBSCRIBED" }
The code field is currently used by the billing endpoints (see below). Treat error as a
human-readable string that may change; branch on status and code, not on message text.
HTTP status codes
| Status | Meaning | Typical causes |
|---|---|---|
200 OK | Success. | Reads, and writes that don't create a resource. |
201 Created | Resource created. | Assessment submissions. |
204 No Content | Success, empty body. | TTS produced no audio. |
400 Bad Request | Validation failed. | Missing required field, bad code format, invalid email/plan. |
401 Unauthorized | Missing/invalid token on a required route. | Expired/absent JWT. |
403 Forbidden | Authenticated but insufficient role, or session mismatch. | Non-provider hitting provider routes. |
404 Not Found | Resource doesn't exist or expired. | Unknown code, expired anonymous record. |
500 Internal Server Error | Unhandled server error. | DB failure, unexpected exception. |
502 Bad Gateway | Upstream dependency failed. | Biomechanics (Python) or Stripe error. |
503 Service Unavailable | Feature not configured. | Billing called without Stripe keys. |
Machine-readable codes
code | Endpoint | Meaning |
|---|---|---|
ALREADY_SUBSCRIBED | POST /billing/checkout | The user already holds this tier or higher. |
USE_PORTAL_TO_UPGRADE | POST /billing/checkout | Core→Plus upgrade must go through the customer portal. |
Notable per-domain behaviors
- Biomechanics routes wrap upstream failures from the Python analysis service as
502with{ "error": "Biomechanics server error", "message": "..." }. - Billing returns
503 { "error": "Billing not configured" }when Stripe keys are absent, and502 { "error": "Billing service error: ..." }on Stripe API failures. - Lookup email only includes the underlying failure detail when
NODE_ENV !== 'production'; in production it returns a generic message. - Validation is largely hand-rolled in the route handlers (not Zod-enforced at the
boundary). Required fields are checked explicitly and return
400with a specific message.
Handling errors with the SDK
The SDK converts every failure into a typed exception:
import { DigitsApiError, DigitsConnectionError } from '@digits/clinical-sdk';
try {
await digits.billing.checkout('plus');
} catch (err) {
if (err instanceof DigitsApiError) {
// HTTP-level failure
switch (err.code) {
case 'ALREADY_SUBSCRIBED': return showManageBilling();
case 'USE_PORTAL_TO_UPGRADE': return openPortal();
}
if (err.isAuthError) return redirectToLogin(); // 401 / 403
if (err.isNotFound) return show404(); // 404
if (err.isServerError) return retryLater(); // 5xx
console.error(err.status, err.message, err.body);
} else if (err instanceof DigitsConnectionError) {
// Network failure / timeout / aborted — never reached the server
console.error('Connection problem:', err.message, err.cause);
} else {
throw err;
}
}
DigitsApiError fields: status, code?, message, body (parsed response),
request ({ method, path }), plus the getters isAuthError, isNotFound,
isServerError.
Retries
The SDK automatically retries idempotent requests (GET, or calls explicitly flagged
idempotent) on 5xx responses and connection errors, using exponential backoff with jitter
(maxRetries, default 2). POST writes are not retried automatically to avoid creating
duplicate records — handle those failures in your own logic if a retry is safe.