For AI agents: the documentation index is at /docs/formbox/llms.txt. A Markdown version of this page is available at /docs/formbox/aidbox-ui-builder-alpha/external-fhir-servers-as-a-data-backend.md or by requesting it with the Accept: text/markdown header.
Formbox Docs

External FHIR servers as a data backend

Overview

Formbox module uses Aidbox's FHIR API capabilities to store all it's data. But sometimes it's appropriate to use other FHIR backends. Formbox provides a simple and granular way to specify external FHIR servers for data management.

Data Domains

Data used by Formbox has different nature and can have different storage requirements and strategies.

We split data in 4 domains, and last 3 of them can have it's own FHIR backend:

Because system resources are non FHIR resources - there is no FHIR backend configuration for them.

  1. System resources - non-FHIR resources, used by Formbox for extended functionality.
  2. Content resources - Questionnaire resource (the main focus and result of Forms Builder).
  3. Terminology resources - ValueSets and Concepts.
  4. Production resources - QuestionnaireResponse resource and other FHIR resources that contain PHI/Production data.

Storage Defaults

By default all data is stored in Aidbox and there is no need for any configuration.

Package storage requirements

Form packages support the same storage configuration as individual forms, in both standalone Formbox and the Aidbox module:

  • form-store handles the package's Questionnaire resources.
  • data-store handles PlanDefinition, RequestGroup, Task, QuestionnaireResponse, and extraction results. This includes the package definition, even though it is reusable content.

Package links, draft saves, conditions, prefills, submission, and amendments all use these stores. The external data store must support FHIR transaction bundles. Package submission sends status updates, responses, and extraction results in one bundle, using the same proxy mechanism as ordinary form extraction.

Each bundle is committed or rolled back by its data store, for both local and external storage. Separate writes within an operation are not covered by a shared transaction, and Formbox does not coordinate rollback across servers. See the package submission API for failure handling.

Storage Configuration

Storage configuration lives in default SDCConfig resource. (read more)

IMPORTANT: config should have default: true property set

Storage Configuration structure

For every domain we have separate key:

  • form-store - content resources domain
  • data-store - production resources domain
  • term-server - terminology resources domain

One more key names a service rather than a storage domain:

  • dtr-service - external Da Vinci DTR service, see DTR service

To enable external FHIR Backend we should specify next fields for it:

  • endpoint - URI to FHIR API
  • headers (optional) - Additional HTTP headers for requests (Authorization for example)
  • auth (optional) - OAuth2 client_credentials credentials, see Authentication

Example:

[store-key]:
    endpoint: 'http://my-ehr.com/fhir'
    headers:
        Authorization: 'Basic cm9vdDpzZWNyZXQ='

Authentication

A server that accepts a fixed credential takes it from headers, as in the example above.

A server that issues tokens is described with auth instead. Formbox requests an access token with the OAuth2 client_credentials grant on the first call and reuses it until it expires, then sends it as Authorization: Bearer <token>. A bearer obtained this way replaces an Authorization header set in headers.

  • token-endpoint - OAuth2 token endpoint that issues the access token
  • client-id - OAuth2 client id
  • client-secret - OAuth2 client secret
  • scope (optional) - scope requested with the token
data-store:
    endpoint: 'https://my-ehr.com/fhir'
    auth:
        token-endpoint: 'https://my-ehr.com/oauth2/token'
        client-id: 'formbox'
        client-secret: 'ILUcZS3T...'
        scope: 'system/*.read'

Where the client secret lives

Formbox takes the secret from the first source that has it:

  1. A secret in the vault - the recommended option: the value stays in a mounted file and never reaches the database. Point the field at a secret from the vault configuration (BOX_VAULT_CONFIG) and scope that secret to SDCConfig:

    auth:
        token-endpoint: 'https://my-ehr.com/oauth2/token'
        client-id: 'formbox'
        _client-secret:
            extension:
            - url: 'http://hl7.org/fhir/StructureDefinition/data-absent-reason'
              valueCode: masked
            - url: 'http://health-samurai.io/fhir/secret-reference'
              valueString: my-ehr-client-secret
    
  2. The value written into client-secret - Aidbox encrypts it on write and stores the ciphertext, so the box needs BOX_SECURITY_ENCRYPT_SECRET. Without that setting the resource cannot be saved.

  3. Box settings - BOX_SDC_DTR_CLIENT_ID and BOX_SDC_DTR_CLIENT_SECRET are used when the configuration carries no credentials of its own.

If the token endpoint cannot be reached, or no credentials are found, the call answers with an OperationOutcome naming the token endpoint.

Form Storage

Store Questionnaire resource only

WARN: assembled forms and components are not yet supported with external storage.

form-store:
    endpoint: "https://fhir-server.com/fhir"

Data Storage

Store QuestionnaireResponse and all FHIR Resources except Questionnaire.

This store will be used as source in $populate operations, and as target in $extract+$process-response operations

data-store:
    endpoint: "https://fhir-server.com/fhir"

Terminology service

To learn how to configure a custom terminology server and add terminology servers to the configuration, see Integration with external terminology servers.

DTR service

dtr-service is not a storage domain: it names the service that answers the Da Vinci DTR operations $questionnaire-package and $next-question instead of Formbox. Payers such as MCG and InterQual own the form library behind those operations, so with it set Formbox resolves nothing locally and forwards the whole call.

dtr-service:
    endpoint: 'https://dtr.payer.example'
    auth:
        token-endpoint: 'https://dtr.payer.example/oauth2/token'
        client-id: 'formbox'
        client-secret: 'ILUcZS3T...'

It takes the same endpoint, headers and auth fields as the stores above. See Da Vinci DTR for what is forwarded, how a single form can name a service of its own, and how each tenant reaches its own service.

Full config example:

POST /SDCConfig
content-type: text/yaml

id: cfg-1
resourceType: SDCConfig
name: Config with external fhir servers
default: true
form-store: 
    endpoint: 'http://forms-repo.com'
data-store: 
    endpoint: 'http://my-ehr.com/fhir'
    headers:
        Authorization: 'Basic cm9vdDpzZWNyZXQ='
term-server:
    endpoint: 'http://tx.com/fhir'
dtr-service:
    endpoint: 'https://dtr.payer.example'
    auth:
        token-endpoint: 'https://dtr.payer.example/oauth2/token'
        client-id: 'formbox'
        client-secret: 'ILUcZS3T...'

Response of an external server

The status and the body a server answers with reach the caller as they are: a 400 with an OperationOutcome from the external server arrives at the client as a 400 with that outcome. When the server cannot be reached at all, Formbox answers with an OperationOutcome that names the endpoint.

The server's own response headers and cookies stay at the proxy — a gateway in front of the external server does not leak its session into the Formbox response.

Multitenancy Support

  • Default system SDCCOnfig (for root Organization) does not take effect on tenants
  • Every tenant have their own default config and thus separate FHIR backends setup

Last updated: