About the data4life API

About the data4life API

This section gives you an overview of the data4life API. The SDK handles all communication with the backend servers. This abstracts away much of the know-how needed to communicate with the servers and exposes a number of lean custom models. Integration partners and developers can rely on these models and the exposed methods to interact with the data4life Personal Health Data Platform.

Only logged-in users can run queries and perform actions. When a request is made without a valid access token and a refresh token, the SDK throws an unauthorized exception.

Authentication and authorization

This section covers the authorization project features of the SDK.

  • Authentication is the process of verifying who users are.

  • Authorization is the process of verifying what users have access to.

The SDK automatically handles all authentication and user management tasks. The user login is managed by the data4life auth app to ensure the safety of the user’s credentials. When the login functionality is invoked, the SDK opens a web view with the necessary pages. Or redirects in the case of a web-based app.

Displaying the login screen

To display the login screen to users.

Data4LifeClient.default.presentLogin(on: self, animated: true) { result in
    switch result {
    case .success:
        // Handle result
    case .failure(let error):
        // Handle error
    }
}

Displaying the login screen with custom OAuth 2.0 scopes

Scopes are a mechanism in the OAuth 2.0 protocol to limit an application’s access to a user account. The scope information is displayed to the user in the login screen.

To display the login screen with additional scopes to users, use the presentLogin method.

let scopes = ["example", "scope"]
Data4LifeClient.default.presentLogin(on: self, animated: true, scopes: scopes) { result in
    switch result {
    case .success:
        // Handle result
    case .failure(let error):
        // Handle error
    }
}

Using additional parameters for the login

To display the login screen with additional parameters, for example, with the loginCompletion callback, use the following example:

func presentLogin(on viewController: UIViewController, animated: Bool, scopes: [String]? = nil, presentationCompletion: (() -> Void)? = nil, loginCompletion: @escaping DefaultResultBlock)

Logging out users

To log the currently logged-in user out of the current session, use the logout method.

func logout(completion: @escaping DefaultResultBlock)

Checking if a user is logged in

To check if a user is logged in, use the userLoggedIn method:

func userLoggedIn(_ completion: @escaping DefaultResultBlock)

Receiving updates about the session state

To receive updates about the session state, use the sessionStateDidChange method.

func sessionStateDidChange(completion: @escaping (Bool) -> Void)

Retrieving user account identifier

It is possible to retrieve the User Identifier, which is unique to the account.

public func getUserId(completion: @escaping ResultBlock<String>)

Using debug logging

The SDK supports logging to console for debug configurations. It is disabled by default. To enable it, set the isLoggingEnabled flag on the client to true. However, release configuration is always silent.

Data4LifeClient.default.isLoggingEnabled = true

Handling response threads

All SDK calls accept DispatchQueue as a parameter. The defined queue is used to return results, the main queue is the default.

let queue = DispatchQueue.global(qos: .background)
Data4LifeClient.default.creteRecord(document, queue: queue) { result in
    // Handle result
}

Managing records

The following sections describe how you perform queries and other actions for documents and records.

Use of annotations

The create, update, search and count methods can optionally use annotations as a parameter. This parameter allows to tag records with custom information saved as a list of strings. Annotations can be filtered inside the search and count methods. These annotations cannot contain empty strings, and uppercased characters will be always lowercased, due to some internal functionality, so it’s recommended to use lowercased ones.

Creating a new FHIR record

To create a new record, use the createFhirStu3Record or createFhirR4Record method.

func createFhirStu3Record<R: FhirStu3Resource>(_ resource: R,
    annotations: [String]? = nil,
    completion: @escaping ResultBlock<Record<R>>)
func createFhirR4Record<R: FhirR4Resource>(_ resource: R,
    annotations: [String]? = nil,
    completion: @escaping ResultBlock<Record<R>>)

Creating new FHIR records

To create several new records, use the createFhirStu3Records or createFhirR4Records method. The annotations will be added to all the created records.

func createFhirStu3Records<R: FhirStu3Resource>(_ resources: [R], annotations: [String] = [], completion: @escaping ResultBlock<BatchResult<Record<R>, R>>)
func createFhirR4Records<R: FhirR4Resource>(_ resources: [R], annotations: [String] = [], completion: @escaping ResultBlock<BatchResult<Record<R>, R>>)

Fetching a FHIR record by its ID

To fetch records for the given ID, use the fetchFhirStu3Record or fetchFhirR4Record method with the identifier parameter of the record.

func fetchFhirStu3Record<R: FhirStu3Resource>(withId identifier: String, of type: R.Type = R.self, completion: @escaping ResultBlock<Record<R>>)
func fetchFhirR4Record<R: FhirR4Resource>(withId identifier: String, of type: R.Type = R.self, completion: @escaping ResultBlock<Record<R>>)

Fetching multiple FHIR records with IDs

To fetch one or more records for the given IDs, use the fetchFhirStu3Records or fetchFhirR4Records method with the identifiers parameters of the records.

func fetchFhirStu3Records<R: FhirStu3Resource>(withIds identifiers: [String], of type: R.Type = R.self, completion: @escaping ResultBlock<BatchResult<Record<R>, String>>)
func fetchFhirR4Records<R: FhirR4Resource>(withIds identifiers: [String], of type: R.Type = R.self, completion: @escaping ResultBlock<BatchResult<Record<R>, String>>)

Fetching multiple FHIR records matching filters

To fetch more records matching a set of optional filters, use the fetchFhirStu3Records or fetchFhirR4Records method with the filter parameters.

func fetchFhirStu3Records<R: FhirStu3Resource>(of type: R.Type = R.self,
                                               size: Int = 10,
                                               page: Int = 1,
                                               from: Date? = nil,
                                               to: Date? = nil,
                                               updatedFrom: Date? = nil,
                                               updatedTo: Date? = nil,
                                               includingDeleted: Bool = false,
                                               annotations: [String] = [],
                                               queue: DispatchQueue = responseQueue,
                                               completion: @escaping ResultBlock<[FhirRecord<R>]>)
func fetchFhirR4Records<R: FhirR4Resource>(of type: R.Type = R.self,
                                           size: Int = 10,
                                           page: Int = 1,
                                           from: Date? = nil,
                                           to: Date? = nil,
                                           updatedFrom: Date? = nil,
                                           updatedTo: Date? = nil,
                                           includingDeleted: Bool = false,
                                           annotations: [String] = [],
                                           queue: DispatchQueue = responseQueue,
                                           completion: @escaping ResultBlock<[FhirRecord<R>]>)

Updating a FHIR record

To update a record, use the updateFhirStu3Record or updateFhirR4Record method. If annotations are set to nil, existing annotations won’t change, otherwise they will override existing ones. If you only need to append new annotations, pass them as a parameter including the old ones in order to maintain them.

public func updateFhirStu3Record<R: FhirStu3Resource>(_ resource: R,
    annotations: [String]? = nil,
    queue: DispatchQueue = responseQueue, completion: @escaping
    ResultBlock<Record<R>>)
public func updateFhirR4Record<R: FhirR4Resource>(_ resource: R,
    annotations: [String]? = nil,
    queue: DispatchQueue = responseQueue, completion: @escaping
    ResultBlock<Record<R>>)

Updating several FHIR records

To update several records, use the updateFhirStu3Records or updateFhirR4Records method. If annotations are set to nil, existing annotations won’t change, otherwise they will override existing ones for all updated records.

func updateFhirStu3Records<R: FhirStu3Resource>(_ resources: [R], annotations: [String]? = nil, completion: @escaping ResultBlock<BatchResult<Record<R>, R>>)
func updateFhirR4Records<R: FhirR4Resource>(_ resources: [R], annotations: [String]? = nil, completion: @escaping ResultBlock<BatchResult<Record<R>, R>>)

Deleting a FHIR record by its ID

To delete a record with its given ID, use the deleteFhirStu3Record or deleteFhirR4Record method with the identifier parameter of the record.

func deleteFhirStu3Record(withId identifier: String, completion: @escaping ResultBlock<Void>)
func deleteFhirR4Record(withId identifier: String, completion: @escaping ResultBlock<Void>)

Deleting multiple FHIR records by their IDs

To delete multiple records with their given IDs, use the deleteFhirStu3Records or deleteFhirR4Records method with the identifiers parameters of the records.

func deleteFhirStu3Records(withIds identifiers: [String], completion: @escaping ResultBlock<BatchResult<String, String>>)
func deleteFhirR4Records(withIds identifiers: [String], completion: @escaping ResultBlock<BatchResult<String, String>>)

Counting FHIR records

To count the stored records per record type, use the countFhirStu3Records or countFhirR4Records method with the given type parameter. If you don’t provide a record type, the client returns the count of all available records of that Fhir Version.

func countFhirStu3Records<R: FhirStu3Resource>(of type: R.Type?,
    annotations: [String] = [],
    completion: @escaping ResultBlock<Int>)
func countFhirR4Records<R: FhirR4Resource>(of type: R.Type?,
    annotations: [String] = [],
    completion: @escaping ResultBlock<Int>)

Creating a new AppData record

To create a new AppData record, use the createAppDataRecord method or the createCodableAppDataRecord method. The annotations parameter allows to tag records with custom information saved as a list of strings. Annotations can be filtered inside the search and count methods.

func createAppDataRecord(_ data: Data,
                         annotations: [String] = []],
                         queue: DispatchQueue = responseQueue,
                         completion: @escaping ResultBlock<AppDataRecord>)

func createCodableAppDataRecord<D: Codable>(_ codable: D,
                                            annotations: [String] = [],
                                            queue: DispatchQueue = responseQueue,
                                            completion: @escaping ResultBlock<AppDataRecord>)

If the codable version of the create is used, the AppDataRecord has a convenient function to get the resource back:

extension AppDataRecord {
    func getDecodableResource<D: Decodable>(of type: D.Type = D.self) throws -> D
}

Fetching an AppData record by its ID

To fetch AppData records for the given ID, use the fetchAppDataRecord method with the identifier parameter of the record.

func fetchAppDataRecord(withId identifier: String,
                        queue: DispatchQueue = responseQueue,
                        completion: @escaping ResultBlock<AppDataRecord>)

Updating an AppData record

To update an AppData record, use the updateAppDataRecord method or the updateCodableAppDataRecord method. If annotations are set to nil, existing annotations won’t change, otherwise they will override existing ones. If you only need to append new annotations, pass them as a parameter including the old ones in order to maintain them.

func updateAppDataRecord(_ data: Data,
                         recordId: String,
                         annotations: [String]? = nil,
                         queue: DispatchQueue = responseQueue,
                         completion: @escaping ResultBlock<AppDataRecord>)

func updateCodableAppDataRecord<D: Codable>(_ codable: D,
                                            recordId: String,
                                            annotations: [String]? = nil,
                                            queue: DispatchQueue = responseQueue,
                                            completion: @escaping ResultBlock<AppDataRecord>)

Deleting an AppData record by its ID

To delete an AppData record with its given ID, use the deleteAppDataRecord method with the identifier parameter of the record.

public func deleteAppDataRecord(withId identifier: String,
                                queue: DispatchQueue = responseQueue,
                                completion: @escaping ResultBlock<Void>)

Counting AppData records

To count the stored AppData records, use the countAppDataRecords method.

func countAppDataRecords(annotations: [String] = [],
                         queue: DispatchQueue = responseQueue,
                         completion: @escaping ResultBlock<Int>)

Managing resources with attachments

In FHIR, some resources can index a document, clinical note, and other binary objects to make them available to a healthcare system. At the moment attachment which can contain attachment are: - DocumentReference - DiagnosticReport - Medication - Practitioner - Patient - Observation (including its component attachments) - Questionnaire (including its nested items attachments) - QuestionnaireResponse (including its nested items and answers attachments)

Downloading all resource’s attachments with their data payloads

If you want a record to be downloaded with its given ID and its attachments, use the downloadStu3Record method and the identifier parameter of the record.

func downloadStu3Record<R: FhirStu3Resource>(withId identifier: String, completion: @escaping ResultBlock<Record<R>>)

Downloading multiple resource records and all their attachments with their data payloads

If you want one or more records to be downloaded with their given IDs and their attachments, use the downloadStu3Records method and the identifiers parameters of the records.

func downloadStu3Records<R: FhirStu3Resource>(withIds identifiers: [String], of type: R.Type = R.self, completion: @escaping ResultBlock<BatchResult<Record<R>, String>>)

Handling attachments

Downloading attachment data

If a FhirStu3Resource with attachments is fetched using the fetchFhirStu3Record method, all of the attachments only have metadata (for example, title and contentType) but no data payload. To download an attachment including the data payload, use the downloadStu3Attachment method or the downloadStu3Attachments method.

Data4LifeClient.default.downloadStu3Record(withId: "identifier", of: DocumentReference.self) { result in
    guard let document = result.value?.resource else {
        return
    }

    guard let attachments = document.getAttachments() else {
        return
    }

    let data = attachments.first?.getData()
}

Using different size versions

When you implement downloading attachments, and if different options are available, you can specify which version of the attachment to download. When downloading a medium-size or small-size image, the downloaded attachment ID is a composed identifier of the original attachment and the thumbnail ID, separated by the # character. When the downloadType parameter is not specified or is unavailable, the original-size attachment (full-size version) is downloaded. The SDK automatically generates the medium-size and the small-size versions of attachments during attachment creation for resizable attachments. The following file formats support resizable attachments: PNG, TIFF, and JPEG.

public enum DownloadType {
    case full, medium, small
}

Cancelling the request in progress

The downloading attachments methods downloadStu3Attachment and downloadStu3Attachments return an object to cancel the request in progress:

let cancellableRequest = Data4LifeClient.default.downloadStu3Attachments(withIds: identifiers, recordId: documentId) { [weak self] result in
    ...
}

cancellableRequest?.cancel()

Observing the download progress

To get a Progress object, which you can use, for example, for a progress bar, include the onProgressUpdated closure.

 let cancellableRequest = Data4LifeClient.default.downloadStu3Attachments(withIds: identifiers, recordId: documentId,
 onProgressUpdated: { progress in
    DispatchQueue.main.async {
        self.progressView.setProgress(Float(progress.fractionCompleted),
        animated: true)
    }
 }, completion: { [weak self] result in
    ...
 })

Downloading a single attachment with data payload

If you want an attachment to be downloaded including the data payload, use the downloadStu3Attachment method with the parameter of the attachment ID.

func downloadStu3Attachment(withId identifier: String,
    recordId: String,
    downloadType: DownloadType = .full,
    onProgressUpdated: ((Progress) -> Void)? = nil,
    completion: @escaping ResultBlock<Attachment>)
-> Cancellable

Downloading a list of attachments with data payloads

If you want one or more attachments to be downloaded including their data payloads with their given IDs, use the downloadStu3Attachments method with the parameters of the attachment ID.

func downloadStu3Attachments(withIds identifiers: [String],
    recordId: String,
    downloadType: DownloadType = .full,
    onProgressUpdated: ((Progress) -> Void)? = nil,
    completion: @escaping ResultBlock<[Attachment]>)
-> Cancellable

Storing custom identifiers

Most of the FHIR resources support adding custom identifiers per client. The following resources are supported:

  • DocumentReference

  • Observation

  • DiagnosticReport

  • CarePlan

  • Organization

  • Practitioner

  • Patient

  • Questionnaire

You can use these helper functions on all supported resources.

func addAdditionalId(_ id: String)
func setAdditionalIds(_ ids: [String])
func getAdditionalIds() -> [String]?

To add and fetch a custom identifier, use the following.

let document = DocumentReference(...)
document.addAdditionalId("some-custom-identifier")

guard let ids = document.getAdditionalIds() else { return }
let storedIdentifier = ids.first

To overwrite custom identifiers with new values, use the following.

let document = DocumentReference(...)
let identifiers = ["some-custom-identifier-one", "some-custom-identifier-two"]
document.setAdditionalIds(identifiers)

To delete all of the custom identifiers, use the following.

let document = DocumentReference(...)
document.setAdditionalIds([])