FlexpaFlexpa
Developer PortalGet a DemoTry it yourself

Guides

  • Home
  • Quickstart
  • Agent guide
  • Claims data guide
  • Financial data guide
  • Parsing FHIR data

Network

  • Network guide
  • Endpoint directory
  • CHPL directoryNew
  • Directory MCP server

Consent

  • OAuth
  • Patient linking
  • Usage patterns
  • Patient access

Records

  • FHIR API
  • Webhooks
  • DestinationsNew
    • Security model
    • Delivered objects
  • Data Sheet
  • Node SDK
  • SMART Health Links API
  • Terminology
  • Claims to clinicalNew

Misc

  • ChangelogNew
  • Support
  • Flexpa OS
  • We're hiring

Destinations

Forward FHIR data to your own cloud storage after every successful sync. With a destination configured, Flexpa writes each patient's synced resources to your Amazon S3 bucket — so you can build a data lake, run analytics, or load records into your own systems without polling the FHIR API.

Today destinations support Amazon S3. Delivery is push-based: there's nothing to poll, and writes happen automatically as part of each sync.

#How it works

  1. You create an IAM role in your AWS account that lets Flexpa write to a bucket you own.
  2. After every successful sync, Flexpa assumes that role and writes the patient's resources to your bucket as NDJSON.
  3. Each delivery is recorded so you can audit what was written, and when.

#Setup

Configure destinations in Portal under Destinations.

Destinations are configured separately for test and live modes. A sync only delivers to a destination whose mode matches the authorization.

  1. Create the S3 bucket in your AWS account — Flexpa never creates it.
  2. Create an IAM role with the trust policy on the right and write access to the bucket.
  3. In Portal, add a destination with the bucket name, region, and the role ARN. Flexpa generates a unique external ID and shows it once.
  4. Click Test connection to verify Flexpa can assume the role and write to the bucket.
  5. Toggle the destination enabled to begin delivery on the next sync.

The external ID is shown only once, when you create the destination. Copy it into your trust policy's sts:ExternalId condition before closing the dialog. If you lose it, delete the destination and create a new one.

IAM setup

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::919310032435:role/flexpa-customer-data-delivery"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<external-id-from-portal>"
        }
      }
    }
  ]
}

The exact Flexpa role ARN and your external ID are shown in Portal when you create the destination — copy them from there rather than from this page.


#Security model

Flexpa never stores long-lived credentials for your AWS account. Each delivery uses a short-lived, cross-account AWS STS AssumeRole into a role you control and can revoke at any time.

  • Your role's trust policy names a single stable Flexpa role (flexpa-customer-data-delivery), not Flexpa's whole account, so you grant the narrowest possible trust.
  • A unique external ID, generated per destination and shown once at creation, must appear in your trust policy's condition. This mitigates the confused-deputy problem: even if someone learned the Flexpa role ARN, they couldn't make Flexpa write to your bucket without your external ID.
  • You scope the role's permissions to exactly the bucket and flexpa/ prefix you choose.

#Encryption

Objects are written with server-side encryption. By default Flexpa uses SSE-S3 (AES256). To use your own KMS key, provide its ARN when creating the destination and grant the role kms:GenerateDataKey on that key — both the role's IAM policy and the key's resource policy must allow it.

KMS key permissions (optional)

{
  "Effect": "Allow",
  "Action": ["kms:GenerateDataKey", "kms:DescribeKey"],
  "Resource": "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID"
}

#Delivered objects

After each successful sync, Flexpa writes a single newline-delimited JSON (NDJSON) object containing the patient's loaded FHIR resources — one resource per line. The resource set matches what $everything returns for that authorization, including resources derived from claims. Objects are written with content type application/fhir+ndjson.

#Object key layout

Keys follow a fixed, predictable layout so you can build downstream tooling against it:

flexpa/{externalId}/{consentId}/{patientAuthorizationId}/{timestamp}-{syncJobId}.ndjson

Key segments

externalId

Your application's user identifier, passed through the Consent SDK. This is the primary way to join delivered objects back to your own users.

consentId

The consent ID for the session. A consent may produce multiple patient authorizations.

patientAuthorizationId

The patient authorization that produced this delivery.

timestamp

ISO-8601 UTC timestamp of the delivery, with colons and dots normalized to -.

syncJobId

The sync job that produced the data.

Every successful sync writes a new object — the initial connection and each subsequent refresh for MULTIPLE usage authorizations. Objects are never overwritten; the timestamp and syncJobId keep each key unique. Pair destinations with webhooks to be notified the moment a sync completes.

Delivered object

{"resourceType":"Patient","id":"...","name":[]}
{"resourceType":"Coverage","id":"...","status":"active"}
{"resourceType":"ExplanationOfBenefit","id":"...","status":"active"}
{"resourceType":"Encounter","id":"...","class":{}}

Reading NDJSON

import { createInterface } from 'node:readline';

// `stream` is the S3 object body
for await (const line of createInterface({ input: stream })) {
  if (!line) continue;
  const resource = JSON.parse(line);
  // handle resource by resource.resourceType
}
Status TwitterGitHub

© 2026 Flexpa. All rights reserved.

FHIR® is the registered trademark of Health Level Seven International and its use does not constitute endorsement by HL7.