@marmarteam/sdk
v0.1.4
Published
TypeScript client for the Marmar Clinical Decision Support API
Readme
Marmar CDS TypeScript SDK
Typed client for integrating with the Marmar Clinical Decision Support (CDS) API. The SDK provides:
- A strongly-typed
MarmarClientover the core REST API - Built‑in error handling via
ApiError - Webhook signature helpers for secure event handling
- Generated TypeScript types for all OpenAPI and FHIR schemas
Installation
npm install @marmarteam/sdkThe package targets modern Node and browser environments with
fetchavailable. For Node < 18 or custom runtimes, see Client configuration below.
Quickstart
import { createMarmarClient } from '@marmarteam/sdk';
const client = createMarmarClient({
baseUrl: 'https://cds.marmar.life/v1',
apiKey: process.env.MARMAR_API_KEY!,
tenantCode: process.env.MARMAR_TENANT_CODE!,
});
const patient = await client.createOrUpdatePatient({
externalId: 'mrn-123',
demographics: {
firstName: 'Ada',
lastName: 'Lovelace',
sex: 'FEMALE',
},
});
const assessmentQueued = await client.createAssessment({
patient: {
patientId: patient.patientId,
},
medications: [
{
name: 'Metformin',
dosage: { amount: 500, unit: 'mg', frequency: 'BID' },
},
],
});
console.log(`Assessment ${assessmentQueued.assessmentId} queued at ${assessmentQueued.createdAt}`);
const roster = await client.listPatients();
console.log(`Loaded ${roster.patients.length} patients`);Client configuration
createMarmarClient accepts the following configuration:
import type { CreateMarmarClientConfig } from '@marmarteam/sdk';
const config: CreateMarmarClientConfig = {
baseUrl: 'https://cds.marmar.life/v1',
apiKey: process.env.MARMAR_API_KEY!,
tenantCode: process.env.MARMAR_TENANT_CODE!,
// Optional: custom fetch implementation (for Node < 18, tests, or edge runtimes)
// fetch: yourCustomFetch,
};baseUrl: Base URL for the Marmar CDS API. The SDK appends relative paths such as/patientsand/assessments.apiKey: Tenant API key. Sent asX-API-KeyandX-Marmar-Api-Key.tenantCode: Human‑readable tenant code used to scope tenant‑specific operations.fetch: Optional customfetchimplementation. If omitted, the globalfetchis used.
Per‑request overrides and options are available via RequestOptions:
const roster = await client.listPatients({
tenantCode: 'another-tenant', // override configured tenant for this call
signal: abortController.signal,
headers: {
'X-Request-Id': 'example-id',
},
});MarmarClient methods
Tenants
registerTenant
Register a new tenant and obtain credentials (primarily for sandbox / provisioning flows).
const registration = await client.registerTenant({
organizationName: 'Example Health',
contactEmail: '[email protected]',
});
console.log(registration.tenantCode, registration.apiKey);Patients
createOrUpdatePatient
Create or update a patient profile for the current tenant.
const patient = await client.createOrUpdatePatient({
externalId: 'mrn-123',
demographics: {
firstName: 'Ada',
lastName: 'Lovelace',
sex: 'FEMALE',
},
});
console.log(patient.patientId, patient.created ? 'created' : 'updated');listPatients
List patients for the current tenant.
const patients = await client.listPatients();
for (const item of patients.patients) {
console.log(item.patientId, item.externalId);
}Assessments
createAssessment
Queue a medication safety assessment for a patient.
const assessmentQueued = await client.createAssessment({
patient: {
patientId: patient.patientId,
},
medications: [
{
name: 'Metformin',
dosage: { amount: 500, unit: 'mg', frequency: 'BID' },
},
],
});getAssessment
Retrieve an assessment by identifier.
const assessment = await client.getAssessment(assessmentQueued.assessmentId);
console.log(assessment.assessmentId, assessment.summary?.text);Webhooks
configureTenantWebhook
Configure outbound webhook delivery for the tenant.
await client.configureTenantWebhook({
url: 'https://example.org/webhooks/marmar',
secret: process.env.MARMAR_WEBHOOK_SECRET!,
events: ['assessment.completed'],
});The same secret is used to generate and verify webhook signatures (see below).
Webhook verification
The SDK includes helpers for securely verifying webhook signatures and generating signatures for tests:
verifyWebhookSignaturesignWebhookPayload
Verifying incoming webhooks
import { verifyWebhookSignature } from '@marmarteam/sdk';
const webhookSecret = process.env.MARMAR_WEBHOOK_SECRET!;
// Example using Express with a raw body parser
app.post(
'/webhooks/marmar',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header('X-Marmar-Signature')!;
const timestamp = req.header('X-Marmar-Timestamp')!;
const payload = req.body; // Buffer or string
const result = verifyWebhookSignature({
payload,
signature,
secret: webhookSecret,
timestamp,
// Optional: toleranceSeconds (defaults to 300 seconds)
// toleranceSeconds: 600,
});
if (!result.valid) {
console.warn('Invalid Marmar webhook:', result.reason);
return res.sendStatus(400);
}
const eventJson = payload.toString('utf-8');
const event = JSON.parse(eventJson);
// Handle AssessmentCompletedEvent
// ...
return res.sendStatus(204);
},
);Generating signatures (tests and local tooling)
import { signWebhookPayload } from '@marmarteam/sdk';
const secret = 'test-secret';
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = { type: 'assessment.completed' };
const signature = signWebhookPayload(secret, payload, timestamp);Error handling
API errors throw an instance of ApiError, which includes the HTTP status and parsed response body when available.
import { ApiError } from '@marmarteam/sdk';
try {
const assessment = await client.getAssessment('non-existent-id');
console.log(assessment);
} catch (error) {
if (error instanceof ApiError) {
console.error('Marmar API error', {
status: error.status,
statusText: error.statusText,
body: error.body,
});
} else {
console.error('Unexpected error', error);
}
}Using generated types and FHIR helpers
The SDK exports the full OpenAPI types from the Marmar CDS API so you can type your own HTTP calls and data pipelines.
import type { components, operations } from '@marmarteam/sdk';
type PatientRequest = components['schemas']['PatientRequest'];
type PatientResponse = components['schemas']['PatientResponse'];
type AssessmentQueuedResponse = components['schemas']['AssessmentQueuedResponse'];
type SubmitFhirBundleResponse =
operations['submitFhirBundle']['responses'][200]['content']['application/json'];You can also use the FHIR types when constructing bundles for ingestion:
import type { components } from '@marmarteam/sdk';
type FhirBundle = components['schemas']['FhirBundle'];
const bundle: FhirBundle = {
resourceType: 'Bundle',
type: 'collection',
entry: [
// Patient, MedicationStatement, Condition, etc.
],
};The FHIR endpoints are currently accessed via standard HTTP clients; the SDK provides the types so that your requests and responses remain type‑safe.
Environment support
- Node: Recommended on Node 18+ where
fetchis available by default. For older Node versions, supply afetchimplementation. - Browser / front‑end: Works with modern browsers that provide
fetch. Do not expose secret API keys directly in public clients.
Further reading
For full API reference, FHIR details, and end‑to‑end integration guides, see the Marmar CDS documentation at https://docs.cds.marmar.life.
