Requests

To get information from Freezerworks or send information to Freezerworks we make requests to the API. Every API request requires some form of authentication. Please see the Authentication section for more information.

Endpoints

Different parts of the API are called endpoints. Each endpoint has a different function, is available at a unique URL path, and may be accessible by more than one HTTP method.

Freezerworks endpoints all take the form "/api/v1/endpoint". Currently, there is only one published version of the API. "v1" is still required for all requests. The version exists to support possible future non-backwards compatible changes to the API.

Each endpoint is detailed in the interactive documentation. The precise URL to use, the data to send, and the HTTP method to use are all fully explained.

HTTP Methods

With a RESTful API, like the one in Freezerworks, the HTTP methods can often be thought of like a verb. The endpoint is the noun and the method tells us what we want to do with that noun. For example, you may have an endpoint like /users. When accessed with the GET method it returns a list of users. When accessed via the POST method it can create a new user in the system. The same endpoint will have different request requirements (permissions, data, etc.) for different HTTP methods.

In general, HTTP methods are used as follows:
  • GET - typically returns a list or details of a single object. No data is changed.
  • POST - used to create a new record in Freezerworks (Sample, Aliquot, User , etc.) or to trigger an action that requires additional data.
  • DELETE - deletes a record in Freezerworks.
  • PATCH - update a record in Freezerworks by sending only the changed data.
  • PUT - update a record in Freezerworks by sending a full record to replace the current one.

While this is list is generally correct, please refer to the detailed endpoint documentation for what a particular HTTP method means in context.

Path and Query Parameters

Information can be passed into the API in a few ways. Two ways involve the URL, or path, that we use to reach the endpoint.

In the first way, we can pass information as part of the URL itself. If we want to get the Aliquots belonging to a specific Sample (with a Freezerworks ID of 100004), we could make a request to the URL: /api/v1/samples/100004/aliquots.

The second way is via query parameters. In this case we provide extra information in our request's URL, but they come after the path. The first parameter is separated from the path with a question mark (?) and the key/value pairs are separated with an equal sign(=). If we wanted to return a list of saved searches, but only wanted the first 5 records we could set a limit via a query parameter by making a request like this: /api/v1/searches?limit=5

Query parameters can be chained by separating the sets of key/value pairs with an ampersand (&). We could get the next set of searches from our previous query with the following request: /api/v1/searches?limit=5&offset=5

Query parameters are usually used with GET requests because GET requests do not contain a request body and can only send data via the URL.

Request Body

Many HTTP methods, like POST, can accept additional data within the request body. For Freezerworks, the request body must be properly formatted JSON. The Content-Type header should be set to "application/json". The actual data that can or must be sent depends on the endpoint and the action that is being performed.

To check if a list of Aliquots are in a status where checking them in would be allowed a POST request to the url /api/v1/aliquots/verifyAction could be made providing the following JSON as the request body:


{
  "values": [102992,102993,202100,100088],
  "fieldName": "PK_AliquotUID",
  "action": "checkIn"
}

While all Freezerworks API responses return Siren formatted data, the format of request Body data is unique and can be found in the endpoint documentation.

Pagination and Partial Responses

Some endpoints can return large collections of records. Retrieving a few million aliquots at once will likely not be very performant. Instead, you can retrieve a subset of records and retrieve more records as you need them. This is referred to as pagination.

The Freezerworks API supports pagination via limits, offsets, and data set IDs.

You can request a subset of records by including the 'limit' parameter in your API request. In this case we will only get back 10 or fewer Aliquot records.


https://localhost/api/v1/aliquots/limit=10

To get a different subset of records we can include both the 'limit' and an 'offset'. If the limit splits our records into pages, the offset is how we request a specific page of records. We could get the next set of records with this request:


https://localhost/api/v1/aliquots/limit=10&offset=1

Each of these responses is a fresh query. In some cases that can be slower than we'd like. To improve performance some Freezerworks endpoints support data set IDs. These temporarily cache a large set of data from an API request (say our giant list of Aliquots) and then use that cached set to more quickly return subsets of records as they are requested. The data set ID will be returned by the first request if it is available. See Responses for an example response.

To make use of a data set include it in the request URL:


https://localhost/api/v1/aliquots/limit=10&offset=1&datasetid=d3320a

Data sets expire over time to reduce resource usage on the Freezerworks server. If the data set ID is no longer valid, a new query will be run and a new data set id returned automatically.

A Full Sample Request

In this example, we'll make three requests to the Freezerworks API. First to get a JWT for authentication. Next to get a list of Aliquots that belong to a Sample. Finally, with that list we'll make a request to the API to see which aliquots are available to for requisitioning.

All the code is in Python 3 and based on the code auto-generated for us by the interactive documentation. Comments within the code (lines starting with #) provide additional explanation. This is not production-ready code as it does not contain error handling and hard codes the username and password.


# importing the requests module because it is a little friendlier than the
# built-in http.client module
import base64
import requests

# hard code a few variables here for demo purposes:
username = "demoUser"
password = "demoPassword"


#######################################################################
################  Request 1: Authentication ###########################
#######################################################################
# First thing, request a JWT. We'll need that to make more requests.
# This is one of the few endpoints that requires us to send the username
# and password. Once we have the JWT we'll use that instead.
jwt_url = "https://localhost/api/v1/users/authenticate"

# Build the headers we'll send with our request. Here we need to let the API
# know we're sending JSON formatted data and provide the Authorization info.
# Base64 encode the username and password in this format: username:password
# Note that for this Python function it needs to be a bytes like object.
encodedUserPass = base64.b64encode(f'{username}:{password}'.encode())  # ZGVtb1VzZXI6ZGVtb1Bhc3N3b3Jk
headers = {
  "Content-Type": "application/json",
  "Authorization": "Basic " + encodedUserPass.decode()
}

# Make the request to the API
response = requests.request("GET", jwt_url, headers=headers)

# The response will be Siren formatted. See the Responses section for more.
# Convert the response from text to JSON (really a Python dict) and then pull out the token.
# Here, we are assuming success. In real code we'd need to check for errors.
jwt = response.json()['properties']['token']


#######################################################################
############  Request 2: Aliquots for a Sample  #######################
#######################################################################
# Now that we have our token, we can make requests to other endpoints using
# bearer token auth.

# Let's get all the aliquots that belong to a particular Sample with
# a Freezerworks ID of 100001.
sample_url = "https://localhost/api/v1/samples/100001"

# Note for this request we are using a different authorization header value.
# Also note that we haven't done anything with the JWT token. I don't need to
# read it or edit it, just send it back with the next request.
headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer " + jwt
}

sample_detail_response = requests.request("GET", sample_url, headers=headers)

# We can do the same conversion of the response and then iterate over all the aliquots
# belonging to this sample to build a list of aliquot numbers.
aliquots_to_check = []
for aliquot in sample_detail_response.json()['properties']['aliquots']:
    aliquots_to_check.append(aliquot['PK_AliquotUID'])


#######################################################################
############   Request 3: Verify the Aliquots   #######################
#######################################################################
# Next we can take our list of aliquots and see if they are available to be requisitioned
# We don't need to make any update to our 'headers' this time, but we do need to build a
# body payload and make a POST request to send it in.
verify_url = "https://localhost/api/v1/aliquots/verifyAction"

payload = {
    "values": aliquots_to_check,
    "fieldName": "PK_AliquotUID",
    "action": "requisition"
}

verify_response = requests.request("POST", verify_url, json=payload, headers=headers)

# With our final response from the API, we can print the list of aliquots which
# can be requisitioned. In a real application we could then take those aliquots
# and make request for them in Freezerworks, or generate and print labels for
# them, or integrate with another system to notify them that these aliquots meet
# their requirements.
print(verify_response.json()['properties']['aliquots']['verified'])