Managing FHIR resources using FHIR bundles

This page explains how to manage FHIR resources by executing FHIR bundles. The ExecuteBundle method implements the FHIR standard batch/transaction interaction (DSTU2, STU3, and R4).

FHIR bundles

A FHIR bundle contains an array of entries, each of which represents an operation, such as create, update, or delete, on a resource, such as an Observation or a Patient. See the detailed descriptions for the elements in the Bundle resource.

When a FHIR bundle is executed, the bundle type determines how the operations in the bundle are performed. Setting the bundle type to batch executes the operations as multiple independent requests. Setting the bundle type to transaction executes the operations as multiple requests that depend on one another. For example, if a transaction bundle includes creating a Patient and an Observation, if the Patient creation request fails then the Observation is not created.

If an operation fails when the bundle type is batch, the Cloud Healthcare API executes the remaining operations in the bundle. If an operation fails when the bundle type is transaction, the Cloud Healthcare API stops executing operations and rolls back the transaction.

Granting permissions for executing bundles

The datasets.fhirStores.fhir.executeBundle permission role is required to execute bundles. To grant this permission, use the healthcare.fhirResourceReader role. For the steps to grant this permission, see Modifying a policy.

The Cloud Healthcare API checks permissions for each operation in the bundle. If you have healthcare.fhirResources.create permission but not healthcare.fhirResources.update permission, you can only execute bundles containing healthcare.fhirResources.create operations.

Executing a bundle

To execute a FHIR bundle, use the projects.locations.datasets.fhirStores.fhir.executeBundle method.

In the following samples, BUNDLE.json is the path and filename to a JSON-encoded FHIR bundle. You can also include the bundle in the request body.

The following sample Bundle creates a Patient resource and deletes another Patient resource:

{
  "resourceType": "Bundle",
  "id": "bundle-transaction",
  "meta": {
    "lastUpdated": "2018-03-11T11:22:16Z"
  },
  "type": "transaction",
  "entry": [
    {
      "resource": {
        "resourceType": "Patient",
        "name": [
          {
            "family": "Smith",
            "given": [
              "Darcy"
            ]
          }
        ],
        "gender": "female",
        "address": [
          {
            "line": [
              "123 Main St."
            ],
            "city": "Anycity",
            "state": "CA",
            "postalCode": "12345"
          }
        ]
      },
      "request": {
        "method": "POST",
    "url": "Patient"
      }
    },
    {
      "request": {
        "method": "DELETE",
        "url": "Patient/1234567890"
      }
    }
  ]
}

The following samples show how to execute a bundle.

curl

To execute a bundle, make a POST request and specify the following information:

  • The name and location of the parent dataset and FHIR store
  • The location of the bundle file in Cloud Storage
  • An access token

The following sample shows a POST request using curl:

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

Regardless of the outcome of the individual operations, after executing a batch bundle, the server returns a JSON-encoded representation of a Bundle resource of type batch-response. The Bundle resource contains one entry for each entry in the request with the outcome of processing the entry, which could be a mix of success and error results.

If a transaction bundle is successful, the server returns a JSON-encoded representation of a Bundle resource of type transaction-response containing one entry for each entry in the request with the successful outcome of the operation.

If an error occurs while executing a transaction bundle, the response body does not contain a bundle. Instead, it contains a JSON-encoded OperationOutcome resource describing the reason for the error. Successful operations that were rolled back are not reported in the response.

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the delete operation.

{
  "entry": [
    {
      "response": {
        "location": projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID,
        "status": "201 Created"
      }
    },
    {
      "response": {
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

To execute a bundle, make a POST request and specify the following information:

  • The name and location of the parent dataset and FHIR store
  • The location of the Bundle file in Cloud Storage
  • An access token

The following sample shows a POST request using Windows PowerShell:

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

Regardless of the outcome of the individual operations, after executing a batch bundle, the server returns a JSON-encoded representation of a Bundle resource of type batch-response. The Bundle resource contains one entry for each entry in the request with the outcome of processing the entry, which could be a mix of success and error results.

If a transaction bundle is successful, the server returns a JSON-encoded representation of a Bundle resource of type transaction-response containing one entry for each entry in the request with the successful outcome of the operation.

If an error occurs while executing a transaction bundle, the response body does not contain a bundle. Instead, it contains a JSON-encoded OperationOutcome resource describing the reason for the error. Successful operations that were rolled back are not reported in the response.

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the delete operation.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "location": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir/RESOURCE/RESOURCE_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-03T04:12:47.312669+00:00",
        "status": "200 OK"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

Go

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"

	healthcare "google.golang.org/api/healthcare/v1"
)

// fhirExecuteBundle executes an FHIR bundle.
func fhirExecuteBundle(w io.Writer, projectID, location, datasetID, fhirStoreID string) error {
	ctx := context.Background()

	healthcareService, err := healthcare.NewService(ctx)
	if err != nil {
		return fmt.Errorf("healthcare.NewService: %v", err)
	}

	fhirService := healthcareService.Projects.Locations.Datasets.FhirStores.Fhir

	payload := map[string]interface{}{
		"resourceType": "Bundle",
		"type":         "transaction",
		"entry": []map[string]interface{}{
			{
				"resource": map[string]interface{}{
					"resourceType": "Patient",
					"active":       true,
				},
			},
		},
	}
	jsonPayload, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("json.Encode: %v", err)
	}

	parent := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", projectID, location, datasetID, fhirStoreID)

	call := fhirService.ExecuteBundle(parent, bytes.NewReader(jsonPayload))
	call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
	resp, err := call.Do()
	if err != nil {
		return fmt.Errorf("ExecuteBundle: %v", err)
	}
	defer resp.Body.Close()

	respBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("could not read response: %v", err)
	}

	if resp.StatusCode > 299 {
		return fmt.Errorf("Create: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
	}
	fmt.Fprintf(w, "%s", respBytes)

	return nil
}

Java

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.healthcare.v1.CloudHealthcare;
import com.google.api.services.healthcare.v1.CloudHealthcareScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Collections;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;

public class FhirStoreExecuteBundle {
  private static final String FHIR_NAME = "projects/%s/locations/%s/datasets/%s/fhirStores/%s";
  private static final JsonFactory JSON_FACTORY = new JacksonFactory();
  private static final NetHttpTransport HTTP_TRANSPORT = new NetHttpTransport();

  public static void fhirStoreExecuteBundle(String fhirStoreName, String data)
      throws IOException, URISyntaxException {
    // String fhirStoreName =
    //    String.format(
    //        FHIR_NAME, "your-project-id", "your-region-id", "your-dataset-id", "your-fhir-id");
    // String data = "{\"resourceType\": \"Bundle\",\"type\": \"batch\",\"entry\": []}"

    // Initialize the client, which will be used to interact with the service.
    CloudHealthcare client = createClient();
    HttpClient httpClient = HttpClients.createDefault();
    String baseUri = String.format("%sv1/%s/fhir", client.getRootUrl(), fhirStoreName);
    URIBuilder uriBuilder = new URIBuilder(baseUri).setParameter("access_token", getAccessToken());
    StringEntity requestEntity = new StringEntity(data);

    HttpUriRequest request =
        RequestBuilder.post()
            .setUri(uriBuilder.build())
            .setEntity(requestEntity)
            .addHeader("Content-Type", "application/fhir+json")
            .addHeader("Accept-Charset", "utf-8")
            .addHeader("Accept", "application/fhir+json; charset=utf-8")
            .build();

    // Execute the request and process the results.
    HttpResponse response = httpClient.execute(request);
    HttpEntity responseEntity = response.getEntity();
    if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
      System.err.print(
          String.format(
              "Exception executing FHIR bundle: %s\n", response.getStatusLine().toString()));
      responseEntity.writeTo(System.err);
      throw new RuntimeException();
    }
    System.out.print("FHIR bundle executed: ");
    responseEntity.writeTo(System.out);
  }

  private static CloudHealthcare createClient() throws IOException {
    // Use Application Default Credentials (ADC) to authenticate the requests
    // For more information see https://cloud.google.com/docs/authentication/production
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    // Create a HttpRequestInitializer, which will provide a baseline configuration to all requests.
    HttpRequestInitializer requestInitializer =
        request -> {
          new HttpCredentialsAdapter(credential).initialize(request);
          request.setConnectTimeout(60000); // 1 minute connect timeout
          request.setReadTimeout(60000); // 1 minute read timeout
        };

    // Build the client for interacting with the service.
    return new CloudHealthcare.Builder(HTTP_TRANSPORT, JSON_FACTORY, requestInitializer)
        .setApplicationName("your-application-name")
        .build();
  }

  private static String getAccessToken() throws IOException {
    GoogleCredentials credential =
        GoogleCredentials.getApplicationDefault()
            .createScoped(Collections.singleton(CloudHealthcareScopes.CLOUD_PLATFORM));

    return credential.refreshAccessToken().getTokenValue();
  }
}

Node.js

A sample bundle file is available in the code sample's GitHub repository.

const {google} = require('googleapis');
const healthcare = google.healthcare('v1');
const fs = require('fs');

async function executeFhirBundle() {
  const auth = await google.auth.getClient({
    scopes: ['https://www.googleapis.com/auth/cloud-platform'],
  });

  google.options({auth, headers: {'Content-Type': 'application/fhir+json'}});

  // TODO(developer): uncomment these lines before running the sample
  // const cloudRegion = 'us-central1';
  // const projectId = 'adjective-noun-123';
  // const datasetId = 'my-dataset';
  // const fhirStoreId = 'my-fhir-store';
  // const bundleFile = 'bundle.json';
  const parent = `projects/${projectId}/locations/${cloudRegion}/datasets/${datasetId}/fhirStores/${fhirStoreId}`;

  const bundle = JSON.parse(fs.readFileSync(bundleFile));

  const request = {parent, requestBody: bundle};
  const resource = await healthcare.projects.locations.datasets.fhirStores.fhir.executeBundle(
    request
  );
  console.log('FHIR bundle executed');
  console.log(resource.data);
}

executeFhirBundle();

Python

A sample bundle file is available in the code sample's GitHub repository.

def execute_bundle(
    base_url, project_id, cloud_region, dataset_id, fhir_store_id, bundle,
):
    """Executes the operations in the given bundle."""
    url = "{}/projects/{}/locations/{}".format(base_url, project_id, cloud_region)

    resource_path = "{}/datasets/{}/fhirStores/{}/fhir".format(
        url, dataset_id, fhir_store_id
    )

    # Make an authenticated API request
    session = get_session()

    headers = {"Content-Type": "application/fhir+json;charset=utf-8"}

    with open(bundle, "r") as bundle_file:
        bundle_file_content = bundle_file.read()

    response = session.post(resource_path, headers=headers, data=bundle_file_content)
    response.raise_for_status()

    resource = response.json()

    print("Executed bundle from file: {}".format(bundle))
    print(json.dumps(resource, indent=2))

    return resource

Resolving references to resources created in a bundle

Resources in a transaction bundle can contain references to resources that don't exist in the target system but are created during bundle execution. The Cloud Healthcare API resolves the association between the resources using the entry.fullUrl field. References that match the entry.fullUrl value of another resource in the bundle are rewritten to the ID of the corresponding resource in the store. This succeeds regardless of the ordering of the operations in the bundle.

The Cloud Healthcare API accepts the fullUrl in the following formats:

  • urn:uuid:UUID
  • urn:oid:OID
  • any URL
  • a resource name in the format RESOURCE_TYPE/RESOURCE_ID, such as Patient/123. Using this format is not recommended because the fullUrl is a placeholder local to the bundle. This can create confusion if a resource in the store has the same name but the resource in the bundle resolves to a different name as a result of a create operation.

The following sample bundle creates a Patient resource and an Observation resource that refers to the Patient resource.

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry":[
    {
      "request": {
        "method":"POST",
        "url":"Patient"
      },
      "fullUrl": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9",
      "resource": {
        "resourceType":"Patient",
        "gender":"male"
      }
    },
    {
      "request": {
        "method":"POST",
        "url":"Observation"
      },
      "resource": {
        "resourceType":"Observation",
        "subject": {
          "reference": "urn:uuid:05efabf0-4be2-4561-91ce-51548425acb9"
        },
        "status":"preliminary",
        "code": {
          "text":"heart rate"
        }
      }
    }
  ]
}

The following samples show how to execute a bundle.

curl

A sample bundle file is available in the code sample's GitHub repository.

To execute a bundle, make a POST request and specify the following information:

  • The name and location of the parent dataset and FHIR store
  • The location of the Bundle file in Cloud Storage
  • An access token

The following sample shows a POST request using curl:

curl -X POST \
    -H "Content-Type: application/fhir+json; charset=utf-8" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    --data @BUNDLE_FILE.json \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir"

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the operation to create the Observation and includes the ID of the new resource.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}

PowerShell

A sample bundle file is available in the code sample's GitHub repository.

To execute a bundle, make a POST request and specify the following information:

  • The name and location of the parent dataset and FHIR store
  • The location of the Bundle file in Cloud Storage
  • An access token

The following sample shows a POST request using Windows PowerShell:

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-RestMethod `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/fhir+json" `
  -InFile BUNDLE_FILE.json `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/fhirStores/FHIR_STORE_ID/fhir" | ConvertTo-Json

The following sample bundle is the output from successfully executing the above example. The first entry indicates the success of the operation to create a Patient and includes the ID of the new resource. The second entry indicates the success of the operation to create the Observation and includes the ID of the new resource.

{
  "entry": [
    {
      "response": {
        "etag": "ETAG1",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Patient/PATIENT_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    },
    {
      "response": {
        "etag": "ETAG",
        "lastModified": "2020-08-04T16:14:14.273976+00:00",
        "location": "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/REGION/datasets/REGION/fhirStores/FHIR_STORE_ID/fhir/Observation/OBSERVATION_ID/_history/HISTORY_ID",
        "status": "201 Created"
      }
    }
  ],
  "resourceType": "Bundle",
  "type": "transaction-response"
}