Migration using SFM DataImporter API

The preferred method for data migration to SFM is to use the SFM DataImporter API directly from the third party EHR system as opposed to using file export and SFM FileUploader for import.

The SFM DataImporter API is used to import organization data and patient data to SFM. This is the same API as the SFM FileUploader is using.

The organization and pasient files are compressed and encrypted as described here: Export file format and structure. The only difference is that the files does not need to be stored on the file system, but may reside in memory.

When performing a migration, that is running a set of file uploads, the migration has to be given a SessionId (this is automatically done by the SFM FileUploader when that is in use). SessionId should be a unique identifier of type GUID, and it should be part of the meta data in the requests. See example below.

The files are uploaded using the SFM.DataImporterAPI in the folowing sequence:

  • The organization file
  • Each pasientfile

Using the API

The SFM.DataImporterAPI uses the TUS protocol for the upload. This is a protocol that is suitable for file uploads, and makes it possible to resume an upload if it should be interrupted. Read more : Resumable file upload protocol v.1.1

The TUS protocol is built on HTTP for "resumable file uploads". This means that the file upload can be interrupted and resumed at any time. The interruption itself can be user-controlled by the user deciding to pause upload, or by accident, e.g. communication problems or server downtime.

When uploading a file, the client sends a POST request to the SFM.DataImporterAPI to initiate the upload. If the API approves the upload, a positive response is returned with 201 Created and upload URL in location. Upload URL is a unique identifier for this upload. The API accept parallel calls, but no more than 5-10 parallel calls should be posted at a time.

Example (bearer token shortened):

    POST /files/ HTTP/1.1
    Authorization: Bearer
    eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0Q0FFNDUyQzhCNkE4OTNCNkE4NDBBQzhDODRGQjA3MEE
    0MjZFNDEiLCJ4NXQiOiJ0TXJrVXNpMnFKTzJxRUNzaklUN0J3cENia0UiLCJ0eXAiOiJKV1QifQ
    Tus-Resumable: 1.0.0
    Upload-Length: 964736
    Upload-Metadata: name
    ZDE0ZDZkZmItNDg0Yi00ZGM0LTgyNzItMWNkODVjNjhmM2Y5LnBhdA==,contentType
    YXBwbGljYXRpb24vb2N0ZXQtc3RyZWFt,FileType UGF0aWVudEZpbGU=,SessionId
    M2E2ODM2NWYtNGRlYy00ZWNmLWIzZTctMDRiZWRiZGQxMDk2
    Host: 127.0.0.1:8080
    Content-Length: 0
    Accept-Encoding: gzip, deflate
    Connection: close

The POST request contains metadata such as:

  • file name: d14d6dfb-484b-4dc4-8272-1cd85c68f3f9.pat,
  • contentType: application / octet-stream
  • FileType: PatientFile

Response:

    HTTP/1.1 201 Created
    Date: Wed, 13 Oct 2021 13:12:00 GMT
    Content-Length: 0
    Connection: close
    Location: /files/6242e436005b45818557f851beed0d78
    Upload-Expires: Wed, 13 Oct 2021 18:12:00 GMT
    Tus-Resumable: 1.0.0
    Strict-Transport-Security: max-age=15724800; includeSubDomains

Response contains the upload URL in the Location header.

Then follows a HEAD request to check if the server has already received data: Request:

    HEAD /files/6242e436005b45818557f851beed0d78 HTTP/1.1
    Authorization: Bearer
    eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0Q0FFNDUyQzhCNkE4OTNCNkE4NDBBQzhDODRGQjA3MEE
    0MjZFNDEiLCJ4NXQiOiJ0TXJrVXNpMnFKTzJxRUNzaklUN0J3cENia0UiLCJ0eXAiOiJKV1QifQ
    F3ErIwddDrSs7DWm3Pug
    Tus-Resumable: 1.0.0
    Host: 127.0.0.1:8080
    Accept-Encoding: gzip, deflate
    Connection: close

Response:

    HEAD /files/6242e436005b45818557f851beed0d78 HTTP/1.1
    Authorization: Bearer
    eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0Q0FFNDUyQzhCNkE4OTNCNkE4NDBBQzhDODRGQjA3MEE
    0MjZFNDEiLCJ4NXQiOiJ0TXJrVXNpMnFKTzJxRUNzaklUN0J3cENia0UiLCJ0eXAiOiJKV1QifQ
    F3ErIwddDrSs7DWm3Pug
    Tus-Resumable: 1.0.0
    Host: 127.0.0.1:8080
    Accept-Encoding: gzip, deflate
    Connection: close

Upload-Offset contains 0 which means that the server has not received data for this file before.

Then follows the actual file upload in a PATCH request:

    PATCH /files/6242e436005b45818557f851beed0d78 HTTP/1.1
    Tus-Resumable: 1.0.0
    Upload-Offset: 0
    Upload-Checksum: sha1 zxQHcWM8gI34UX+3KS2MUNJe/lQ=
    Content-Type: application/offset+octet-stream
    Host: dataimport.qa.forskrivning.no
    Content-Length: 524288
    Accept-Encoding: gzip, deflate
    Connection: close

    [data]

After successful upload, the client receives the following response:

    HTTP/1.1 204 No Content
    Date: Wed, 13 Oct 2021 13:12:57 GMT
    Connection: close
    Tus-Resumable: 1.0.0
    Upload-Offset: 524288
    Upload-Expires: Wed, 13 Oct 2021 18:12:00 GMT
    Strict-Transport-Security: max-age=15724800; includeSubDomains