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 directory
  • 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 clinical

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 named exactly flexpa-data-delivery (the name is required — any other name is accepted in Portal but fails at delivery), 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. New destinations are enabled by default, so the next successful sync delivers to your bucket automatically — there's no toggle to flip to start delivery. Use the enabled toggle in Portal to pause or resume delivery for an existing destination.

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 must be named exactly flexpa-data-delivery, and its trust policy names a single stable Flexpa role (flexpa-customer-data-delivery), not Flexpa's whole account, so you grant the narrowest possible trust. Flexpa's delivery role is only permitted to assume a role with this exact name.
  • 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 and kms:DescribeKey on that key (see the policy on the right) — 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 FHIR resources loaded by that sync — one resource per line. The initial connection delivers the patient's full record (equivalent to $everything); each subsequent refresh for MULTIPLE usage authorizations delivers only the resources that changed since the previous sync, so a refresh object is incremental rather than the cumulative set $everything returns. Both include 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.