Node SDK
The Node SDK is a TypeScript library for interacting with Flexpa API. It handles OAuth 2.0 authentication, token management, FHIR resource operations, and SMART Health Links.
#Installation
Requires Node.js 19+ (uses the global Web Crypto API).
npm install @flexpa/node-sdk
Import FlexpaClient
import FlexpaClient from '@flexpa/node-sdk';
#Initialize
Choose an initialization method based on your use case:
#fromBearerToken
Initialize from an existing access token.
Parameters
- bearerTokenstringRequired
Access token
From Bearer Token
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
#fromAuthorizationCode
Exchange an OAuth 2.0 authorization code from the PKCE flow.
Parameters
- codestringRequired
Authorization code received from the OAuth redirect
- codeVerifierstringRequired
The original code verifier used to generate the code challenge (43-128 characters)
- redirectUristringRequired
Must match the redirect URI used in the authorization request
- publishableKeystringRequired
Publishable Key. See API Keys.
From Authorization Code
// In your callback handler, after user authorizes
const flexpaClient = await FlexpaClient.fromAuthorizationCode(
code, // From URL query params
codeVerifier, // Retrieved from secure storage
'https://your-app.com/callback',
'pk_live_...'
);
// Store the token for future API calls
const accessToken = flexpaClient.getAccessToken();
#fromClientCredentials
Obtain an application access token for server-to-server calls without patient context. Tokens are valid for 30 minutes.
Test and live mode keys can mint application tokens for SMART Health Links and REST consent management. Each token is restricted to its application's mode. Application-token access to FHIR resources requires live mode.
Parameters
- publishableKeystringRequired
Publishable Key. See API Keys.
- secretKeystringRequired
Secret Key. See API Keys.
From Client Credentials
const flexpaClient = await FlexpaClient.fromClientCredentials(
'pk_live_...',
'sk_live_...'
);
#OAuth PKCE Utilities
The SDK provides helper functions to implement the OAuth 2.0 PKCE flow (RFC 7636).
#generateCodeVerifier
Generate a cryptographically random code verifier for PKCE.
This is an async function that resolves to a 43-character URL-safe random string, so it must be awaited.
Store the code verifier securely on the client. You'll need it to exchange the authorization code for tokens.
Generate Code Verifier
const codeVerifier = await FlexpaClient.generateCodeVerifier();
// Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
#generateCodeChallenge
Generate a code challenge from a code verifier using the S256 method. This is an async function and returns a Promise<string>, so it must be awaited.
Parameters
- codeVerifierstringRequired
The code verifier to hash
Generate Code Challenge
const codeVerifier = await FlexpaClient.generateCodeVerifier();
const codeChallenge = await FlexpaClient.generateCodeChallenge(codeVerifier);
// Example: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
#buildAuthorizationUrl
Build the authorization URL for initiating the OAuth 2.0 PKCE flow.
Open this URL in a browser to start patient authorization.
Parameters
- publishableKeystringRequired
Your publishable key (client_id)
- redirectUristringRequired
Must match a registered redirect URI for your application
- codeChallengestringRequired
The S256 code challenge generated from your code verifier
- externalIdstringRequired
Your application's unique identifier for this user. Enables tracking authorizations and correlating patient data across sessions.
- scopestring[]
OAuth scopes (default: ['launch/patient']). Include 'offline_access' to request MULTIPLE usage.
- statestring
Optional state parameter for CSRF protection
- endpointIdstring
Optional endpoint ID to pre-select a specific health plan. Cannot be combined with flow: { type: 'ial2' }.
- resumeboolean
Set to true to resume an in-progress consent workflow. Emits flexpa_resume=true.
- flowAuthorizationFlow
Opt into a non-default flow. Discriminated union — either { type: 'ial2' } for TEFCA / IAL2 identity-verified flow, or { type: 'search', hints?: { firstName?, lastName?, dob? } } for the search-mode patient match flow. Omit for the standard payer/provider flow.
Build Authorization URL
// 1. Generate PKCE credentials (both helpers are async)
const codeVerifier = await FlexpaClient.generateCodeVerifier();
const codeChallenge = await FlexpaClient.generateCodeChallenge(codeVerifier);
// 2. Build the authorization URL
const authUrl = FlexpaClient.buildAuthorizationUrl({
publishableKey: 'pk_live_...',
redirectUri: 'https://your-app.com/callback',
codeChallenge,
externalId: 'user-123',
});
// 3. Store codeVerifier securely, then redirect user to authUrl
TEFCA / IAL2 flow
const authUrl = FlexpaClient.buildAuthorizationUrl({
publishableKey: 'pk_live_...',
redirectUri: 'https://your-app.com/callback',
codeChallenge,
externalId: 'user-123',
flow: { type: 'ial2' },
});
#Link API
#introspect
Retrieve token metadata including sync status, patient ID, and available resources. See the Introspect API reference for the full response schema.
Introspect
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const tokenDetails = await flexpaClient.introspect();
#tokenRefresh
Refresh the access token to extend access to patient data.
Only available for MULTIPLE usage authorizations.
Returns a new access token. You should update your stored token with the returned value.
Parameters
- publishableKeystringRequired
Publishable Key
- secretKeystringRequired
Secret Key
Token Refresh
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const { access_token } = await flexpaClient.tokenRefresh(
'pk_live_...',
'sk_live_...'
);
// Store the new access_token for future requests
#revoke
Revoke the access token
Parameters
- secretKeystringRequired
Secret Key
Revoke
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
await flexpaClient.revoke('sk_live_...');
#SMART Health Links
These methods are available in @flexpa/node-sdk 2.1.0. Use an application-token client for server-to-server SHL management:
const client = await FlexpaClient.fromClientCredentials('pk_test_...', 'sk_test_...');
Patient tokens and legacy secret keys also work through FlexpaClient.fromBearerToken(credential). All requests are scoped to the credential's application and mode; patient tokens can manage only their own user's links. New server integrations should use application tokens.
#createSmartHealthLink
Create an encrypted link and receive its id, shl, viewerUrl, manifestUrl, and expiresAt. The exported CreateSmartHealthLinkOptions type describes the creation options.
const link = await client.createSmartHealthLink({
files: [{
contentType: 'application/fhir+json',
content: { resourceType: 'Bundle', type: 'collection', entry: [] },
}],
user: { externalId: 'user-123' },
ttl: 3600,
});
ttl is in seconds and required unless longTerm: true. Direct transfer requires exactly one file and excludes a passcode. Application tokens and secret keys require user.externalId; patient tokens derive the user from their authorization.
#listSmartHealthLinks
Returns ListSmartHealthLinksResponse: a data array of link metadata and successful-access summaries (accessCount, lastAccessedAt), plus meta.hasMore and meta.nextCursor. The optional limit defaults to 20 and accepts 1–1000.
const page = await client.listSmartHealthLinks({ limit: 20 });
if (page.meta.nextCursor) {
const nextPage = await client.listSmartHealthLinks({
limit: 20,
cursor: page.meta.nextCursor,
});
}
#listSmartHealthLinkAccesses
Returns a typed FHIR Bundle of AuditEvents, including successful and denied access attempts. Entries retain their fullUrl identifiers. The optional limit defaults to 20 and accepts 1–100.
Filter status by SUCCEEDED, PASSCODE_REQUIRED, INVALID_PASSCODE, LOCKED, EXPIRED, or REVOKED, and type by MANIFEST or DIRECT.
const filters = { status: 'SUCCEEDED', type: 'MANIFEST', limit: 20 } as const;
const audit = await client.listSmartHealthLinkAccesses(link.id, filters);
const nextUrl = audit.link?.find((entry) => entry.relation === 'next')?.url;
const cursor = nextUrl ? new URL(nextUrl).searchParams.get('cursor') : null;
if (cursor) {
const nextAudit = await client.listSmartHealthLinkAccesses(link.id, {
...filters,
cursor,
});
}
Pass the cursor back with the same filters. The SDK sends the request to its configured API URL; it does not fetch Bundle links automatically. Malformed cursors return 400 through HttpError.
#revokeSmartHealthLink
Revokes the link by ID. Resolves without a value after the API's 204 response.
await client.revokeSmartHealthLink(link.id);
SHL management methods use the SDK's existing retry behavior: up to ten retries on 429, respecting Retry-After. Other failures are returned immediately.
#FHIR API
FHIR methods include built-in retry logic for 429 responses — both the transient 429 returned while a patient's initial sync is running and throttled rate limits. Every method retries; only read and search accept retry options. The SDK does not implement ViewDefinition $run — call that operation directly and handle its 429 responses yourself.
#getCapabilityStatement
Get Capability Statement
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const capabilityStatement = await flexpaClient.getCapabilityStatement();
#read
Read a FHIR resource
Parameters
- resourceTypestringRequired
Supported FHIR Resource
- idstringRequired
Resource ID
- options{ numRetries?: number; delayMs?: number }
Retry options object passed as the third argument. numRetries is the maximum number of retries (default: 10); delayMs is the delay in milliseconds before the first retry (default: 1000), doubling after each attempt. A Retry-After header on the response takes precedence and becomes the base for that doubling. With the defaults and no Retry-After — as with the transient sync 429 — the SDK waits about 17 minutes across 10 retries before throwing an HttpError.
Read
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const patient = await flexpaClient.read('Patient', 'patient-id', { numRetries: 5, delayMs: 2000 });
#search
Search a FHIR resource
Parameters
- resourceTypestringRequired
Supported FHIR Resource
- searchParamsRecord<string, string>Required
FHIR search parameters as key-value pairs
- options{ numRetries?: number; delayMs?: number }
Optional retry options. numRetries — maximum number of retries (default: 10); delayMs — delay in milliseconds before the first retry (default: 1000), doubling after each attempt unless the response carries a Retry-After header, which takes precedence
Search
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const patients = await flexpaClient.search('Patient',
{ given: 'John' },
{ numRetries: 10, delayMs: 1000 }
);
const observations = await flexpaClient.search('Observation',
{ patient: 'patient-id' }
);
#Patient $everything
Retrieve all the resources for the patient
$everything
const flexpaClient = FlexpaClient.fromBearerToken('your-access-token');
const patientData = await flexpaClient.$everything();
#Error Handling
The Node SDK throws HttpError instances for API failures. These errors include the full HTTP response for detailed inspection.
Common Error Codes:
429 - Data still processing (see sync jobs)
422 - Processing error during sync
401 - Invalid credentials
404 - Resource not found
Error Handling
import { HttpError } from '@flexpa/node-sdk';
try {
const patient = await flexpaClient.read('Patient', 'invalid-id');
} catch (error) {
if (error instanceof HttpError) {
console.log('Status:', error.response.status);
// Parse error details
const errorData = await error.json();
console.log('Error details:', errorData);
}
}
#Utilities
#getAccessToken
Retrieve the access token from the client for storage or use with other HTTP clients.
Get Access Token
const flexpaClient = await FlexpaClient.fromAuthorizationCode(...);
// Store for later use with fromBearerToken
const accessToken = flexpaClient.getAccessToken();