Skip to main content

Integration Recipes

Integration Recipes

Last updated: 2026-02-18

Step-by-step guides for the most common enterprise integration scenarios. Each recipe is self-contained — complete it start to finish and you have a working integration.

Prerequisites for all recipes: Complete the Quickstart first (you need an API key).


Recipe 1: HRIS Roster Sync via SCIM

Goal: Automatically provision and deprovision employees from your identity provider (Okta, Azure AD, OneLogin) into Bounded Health.

Time: ~20 minutes

1.1 Enable SCIM for your employer

bash
curl -X POST "$MT_HOST/api/employer/sso" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -H "content-type: application/json" \
  -d '{
    "scimEnabled": true
  }'

Response includes your SCIM bearer token (shown once):

json
{
  "scimEnabled": true,
  "scimBearerToken": "scim_tok_a1b2c3..."
}

Save this token — your IdP will use it.

bash
export SCIM_TOKEN="scim_tok_a1b2c3..."

1.2 Configure your IdP

In your identity provider (Okta, Azure AD, etc.):

  • SCIM endpoint: https://<YOUR_HOST>/api/scim/v2
  • Auth method: Bearer token
  • Token: The scimBearerToken from step 1.1

1.3 Provision a user manually (for testing)

bash
curl -X POST "$MT_HOST/api/scim/v2/Users" \
  -H "authorization: Bearer $SCIM_TOKEN" \
  -H "content-type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "jane.doe@acme.com",
    "name": { "givenName": "Jane", "familyName": "Doe" },
    "emails": [{ "value": "jane.doe@acme.com", "primary": true }],
    "active": true
  }'

Expected response (201 Created):

json
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "id": "user_xyz789",
  "userName": "jane.doe@acme.com",
  "active": true,
  "meta": { "resourceType": "User", "created": "2026-02-18T12:00:00Z" }
}

1.4 Deactivate a user

bash
curl -X PATCH "$MT_HOST/api/scim/v2/Users/user_xyz789" \
  -H "authorization: Bearer $SCIM_TOKEN" \
  -H "content-type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
    "Operations": [{ "op": "replace", "path": "active", "value": false }]
  }'

This soft-deactivates the user and revokes their API keys and sessions.

1.5 Create a directory group

bash
curl -X POST "$MT_HOST/api/scim/v2/Groups" \
  -H "authorization: Bearer $SCIM_TOKEN" \
  -H "content-type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
    "displayName": "Engineering Team",
    "members": [{ "value": "user_xyz789" }]
  }'

Recipe 2: Bulk FHIR Patient Export

Goal: Export all patient data as FHIR-compliant NDJSON for loading into your data warehouse or analytics platform.

Time: ~10 minutes

2.1 Start the export

bash
curl -s "$MT_HOST/api/fhir/\$export" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -H "accept: application/fhir+json" \
  -H "prefer: respond-async" \
  -D - -o /dev/null

Expected response (202 Accepted):

HTTP/2 202 content-location: /api/fhir/bulk-export/exp_abc123 retry-after: 2 x-progress: in-progress

Copy the content-location URL.

2.2 Poll for completion

bash
curl -s "$MT_HOST/api/fhir/bulk-export/exp_abc123" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01"

While running:

json
{ "status": "running", "progress": "in-progress" }

When complete:

json
{
  "status": "succeeded",
  "output": [
    {
      "type": "Patient",
      "url": "/api/fhir/bulk-export/exp_abc123/download",
      "count": 1250
    }
  ]
}

2.3 Download the NDJSON

bash
curl -s "$MT_HOST/api/fhir/bulk-export/exp_abc123/download" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -o patients.ndjson

Each line is a complete FHIR Patient JSON resource.

2.4 Group-level export (optional)

Export only patients in a specific SCIM group:

bash
curl -s "$MT_HOST/api/fhir/Group/grp_abc123/\$export" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -H "accept: application/fhir+json" \
  -H "prefer: respond-async"

Recipe 3: Real-Time Event Processing (Webhooks)

Goal: React to platform events (cohort uploads, simulation completions, export readiness) in real time.

Time: ~15 minutes

3.1 Create your webhook receiver

Node.js / Express:

typescript
import crypto from "crypto";
import express from "express";

const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;

// Track processed event IDs for idempotency
const processedEvents = new Set<string>();

app.post(
  "/webhooks/Bounded Health",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const rawBody = req.body.toString("utf8");

    // Step 1: Verify signature
    const signature = req.headers["x-webhook-signature"] as string;
    const expected = `sha256=${crypto
      .createHmac("sha256", WEBHOOK_SECRET)
      .update(rawBody)
      .digest("hex")}`;

    if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
      return res.status(401).send("Invalid signature");
    }

    // Step 2: Check idempotency
    const webhookId = req.headers["x-webhook-id"] as string;
    if (processedEvents.has(webhookId)) {
      return res.status(200).send("Already processed");
    }

    // Step 3: Process the event
    const event = JSON.parse(rawBody);
    console.log(`[${event.event}]`, JSON.stringify(event.data, null, 2));

    switch (event.event) {
      case "cohort.uploaded":
        console.log("New cohort data available — trigger ETL pipeline");
        break;
      case "simulation.completed":
        console.log("Simulation finished — update dashboards");
        break;
      case "export.ready":
        console.log("Export ready for download — notify ops team");
        break;
      default:
        console.log("Unhandled event type:", event.event);
    }

    // Step 4: Mark as processed and respond quickly
    processedEvents.add(webhookId);
    res.status(200).send("OK");
  }
);

app.listen(3001, () => console.log("Webhook receiver running on :3001"));

3.2 Register the webhook

bash
curl -X POST "$MT_HOST/api/employer/webhooks" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://your-server.example.com/webhooks/Bounded Health",
    "events": ["cohort.uploaded", "simulation.completed", "export.ready"]
  }'

3.3 Test it

bash
curl -X POST "$MT_HOST/api/employer/webhooks/<WEBHOOK_ID>/test" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01"

Watch your terminal for the verified event.


Recipe 4: SSO Setup (SAML with Okta)

Goal: Allow your employees to sign in to Bounded Health using their Okta credentials.

Time: ~30 minutes

4.1 Create a SAML app in Okta

In your Okta admin panel:

  1. Go to ApplicationsCreate App IntegrationSAML 2.0
  2. Configure:
    • Single sign-on URL (ACS): https://<YOUR_MT_HOST>/api/auth/saml/callback/<EMPLOYER_ID>
    • Audience URI (SP Entity ID): https://<YOUR_MT_HOST>/api/auth/saml/metadata/<EMPLOYER_ID>
    • Name ID format: EmailAddress
    • Attribute statements:
      • emailuser.email
      • firstNameuser.firstName
      • lastNameuser.lastName
  3. Download the IdP metadata URL from Okta

4.2 Configure SAML in Bounded Health

bash
curl -X POST "$MT_HOST/api/employer/sso" \
  -H "x-api-key: $MT_API_KEY" \
  -H "x-api-version: 2026-02-01" \
  -H "content-type: application/json" \
  -d '{
    "samlEnabled": true,
    "samlMetadataUrl": "https://your-org.okta.com/app/abc123/sso/saml/metadata",
    "samlIssuer": "https://your-org.okta.com",
    "samlNameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
    "samlEmailAttribute": "email",
    "samlFirstNameAttribute": "firstName",
    "samlLastNameAttribute": "lastName"
  }'

4.3 Test the login flow

Open in a browser:

https://<YOUR_MT_HOST>/api/auth/saml/start/<EMPLOYER_ID>

This should redirect you to Okta → you sign in → you land back in Bounded Health authenticated.

4.4 Azure AD configuration

For Azure AD, the steps are similar:

  • Reply URL: Same ACS URL as above
  • Identifier: Same SP Entity ID as above
  • User Attributes: Map email, givenname, surname
  • Use the App Federation Metadata URL as samlMetadataUrl

Recipe 5: SDK Installation and First Call

Goal: Use the typed TypeScript SDK instead of raw HTTP calls.

Time: ~5 minutes

5.1 Install the SDK

bash
npm install @Bounded Health/enterprise-sdk

5.2 Initialize the client

typescript
import { Bounded HealthClient } from "@Bounded Health/enterprise-sdk";

const client = new Bounded HealthClient({
  apiKey: process.env.MT_API_KEY!,
  baseUrl: process.env.MT_HOST!,
  apiVersion: "2026-02-01",
});

5.3 Make API calls

typescript
// List patients
const patients = await client.fhir.searchPatients({ _count: 10 });
console.log(`Found ${patients.total} patients`);

// List webhook subscriptions
const webhooks = await client.webhooks.list();
console.log(`Active webhooks: ${webhooks.length}`);

// Trigger an export
const exportJob = await client.exports.createCsvExport({
  exportType: "simulation_results",
});
console.log(`Export job queued: ${exportJob.exportId}`);

5.4 Verify webhook signatures with SDK helpers

typescript
import { validateInboundWebhook } from "@Bounded Health/enterprise-sdk/webhooks";

// In your webhook handler:
const result = validateInboundWebhook({
  rawBody: req.body.toString("utf8"),
  signature: req.headers["x-webhook-signature"] as string,
  secret: process.env.WEBHOOK_SECRET!,
  webhookId: req.headers["x-webhook-id"] as string,
  event: req.headers["x-webhook-event"] as string,
  version: req.headers["x-webhook-version"] as string,
  timestamp: req.headers["x-webhook-timestamp"] as string,
});

if (!result.valid) {
  console.error("Webhook validation failed:", result.error);
  return res.status(401).send("Invalid");
}