@intaops/connector
v0.1.3
Published
TypeScript connector for the IntaOps SMART-on-FHIR R4 network
Maintainers
Readme
@intaops/connector — TypeScript
Drop-in TypeScript SDK for the IntaOps SMART-on-FHIR R4 network. Works
in Node ≥ 18 (uses fetch + WebCrypto) and modern browsers.
npm install @intaops/connectorPrerequisites
Before the SDK can call IntaOps, you need OAuth credentials — client_id + client_secret. Get them from the IntaOps Dashboard:
- Sign in → Developer Portal → Applications (
https://intaops.io/applications) - Create an Application — pick
client_credentialsfor backend services, orauthorization_codefor provider / patient-facing apps - Copy the
client_secretwhen shown — it's revealed once. Keep it server-side, never in browser code.
The SDK handles the OAuth token exchange (POST /oauth/token) for you — IntaOpsClient.create({ clientId, clientSecret, ... }) is enough. Access tokens are short-lived (~5 min) and refreshed automatically.
Backend (system-to-system)
import { IntaOpsClient } from "@intaops/connector";
const client = await IntaOpsClient.create({
clientId: "app_live_…",
clientSecret: "secret_live_…",
environment: "live",
scopes: ["system/Patient.read", "system/Observation.read"],
});
const patient = await client.fhir.get("Patient", "u123");
for await (const obs of client.fhir.searchIter("Observation", { patient: "u123" })) {
console.log(obs.valueQuantity);
}Provider / patient app (SMART user grant)
import { IntaOpsClient } from "@intaops/connector";
// Step 1 — kick off PKCE
const pkce = await IntaOpsClient.generatePKCE();
const tmp = await IntaOpsClient.create({
clientId: "app_live_…", environment: "live",
scopes: ["patient/Observation.read"],
});
const url = tmp.authorizeUrl({
redirectUri: "https://yourapp.com/cb",
codeChallenge: pkce.challenge,
});
// redirect user → url, capture ?code= on /cb
// Step 2 — exchange the code
const client = await IntaOpsClient.fromAuthorizationCode({
code,
redirectUri: "https://yourapp.com/cb",
codeVerifier: pkce.verifier,
clientId: "app_live_…",
environment: "live",
scopes: ["patient/Observation.read"],
});Errors
import {
ConsentRequiredError, OperationOutcomeError,
RateLimitedError, OAuthError,
} from "@intaops/connector";
try {
await client.fhir.get("Observation", "obs-1");
} catch (e) {
if (e instanceof ConsentRequiredError) {
// First call to any patient/* scope ALWAYS throws this — consent is OFF
// by default. The IntaOps backend auto-pushes a prompt to the individual's
// IntaOps app; surface e.consentUrl to them (push, email, in-app deep-link)
// and retry once they approve.
notifyUser(e.userId, e.consentUrl);
} else if (e instanceof RateLimitedError) {
await sleep((e.retryAfter ?? 30) * 1000);
} else if (e instanceof OperationOutcomeError) {
log(e.statusCode, e.issueCode, e.location);
} else if (e instanceof OAuthError) {
refreshCredentials();
} else {
throw e;
}
}Scope ↔ industry gating — the scopes your app can request are bounded by your entity's industry. Healthcare entities can request patient/* (consent-gated) and system/* (broad read) FHIR scopes; other industries are scoped to their own data surfaces (financial → financial data, retail → shopping, etc.). Requesting a scope outside your industry throws OAuthError with error === "invalid_scope" at client instantiation.
License
Apache-2.0.
