STARLIMS REST API & POSTMAN – Production Mode

Alright folks! If you’ve been playing with the new STARLIMS REST API and tried production mode, perhaps you’ve run into all kind of problems providing the correct SL-API-Signature header. You may wonder “but how do I generate this?” – even following STARLIMS’s c# example may yield unexpected 401 results.

At least, it did for me.

I was able to figure it out by looking at the code that reconstructs the signature on STARLIMS side, and here’s a snippet of code that works in POSTMAN as a pre-request code:

// required for the hash part. You don't need to install anything, it is included in POSTMAN
var CryptoJS = require("crypto-js");

// get data required for API signature
const dateNow = new Date().toISOString();
// thhis is the API secret found in STARLIMS key management
const privateKey = pm.environment.get('SL-API-secret');
// this is the API access key found in STARLIMS key management
const accessKey = pm.environment.get('SL-API-Auth');
// in my case, I have a {{url}} variable, but this should be the full URL to your API endpoint
const url = pm.environment.get('url') + request.url.substring(8);
const method = request.method;
// I am not using api methods, but if you are, this should be set
const apiMethod = "";

var body = "";
if (pm.request.body.raw){
    body = pm.request.body.raw;
}

// this is  the reconstruction part - the text used for signature
const signatureBase = `${url}\n${method}\n${accessKey}\n${apiMethod}\n${dateNow}\n${body}`;

// encrype signature
var data = CryptoJS.enc.Utf8.parse(signatureBase);
const hash = CryptoJS.HmacSHA256(data, privateKey);
const encodedHash = encodeURIComponent(CryptoJS.enc.Base64.stringify(hash));

// set global variables used in header
pm.globals.set("SL-API-Timestamp", dateNow);
pm.globals.set("SL-API-Signature", encodedHash);

One point of interest – if it still is not working, and if you can’t figure out why, an undocumented STARLIMS feature is to add this application setting in the web.config to view more info:

<add key="RestApi_LogLevel" value="Debug" />

I hope this helps you use the new REST API provided by STARLIMS!

REST API Cloud

STARLIMS REST API – Add and Route your own endpoints

With the version 12 technology platform, STARLIMS offers a new REST API engine. It is really great – until you want to enhance it and add your own endpoints. That’s where it gets … complicated. Well – not so much – if you know where to start. Nothing here is hidden information, it is all written in the technology release documentation; just not easily applied.

If you read the doc, you’ve read something like this:

Routing maps incoming HTTP API requests to their implementation. If you are a Core Product team, you must implement routing in pre-defined Server Script API_Helper.RestApiRouter; if you are a Professional Services or Customer team, you must implement routing in pre-defined Server Script API_Helper_Custom.RestApiRouter (which you need to create, if it doesn’t exist).

STARLIMS Technology Platform Documentation
09-016-00-02 REV AB

That section is accessible using the /building_rest_api.html Url of the platform documentation.

It is really good, and it works, and everything listed is appropriate. I would only add 2 points for your sanity.

1- handle your routes in a different way than what STARLIMS suggest. Their example is very simple, but you’ll want to have something scalable / reusable. I went with a single function and nested hashtables. By default, the custom routing needs a Route method. To “store” the route, I’ll also add a private getRoutes method. In the future, we’ll only add entries in the getRoutes, which will simplify our life.

:PROCEDURE getRoutes;

	/* 
		structure is:
		
		hashTable of version
			hashTable of of service
				hashTable of entity
	;
	:DECLARE hApiVersions;
	
	/* all route definition should be in lowercase;
	
	/* store API Verions at 1st htable level;
	hApiVersions := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v2"] := LimsNetConnect("", "System.Collections.Hashtable");
	
	/* store each service within the proper version;
	hApiVersions["v1"]["examples"] := LimsNetConnect("", "System.Collections.Hashtable");
	/* then store each endpoint per entity;
	hApiVersions["v1"]["examples"]["simple"] := "API_Examples_v1.Simple";

	/* store each service within the proper version;
	hApiVersions["v1"]["system"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"]["system"]["status"] := "API_CustomSystem_v1.status";
	
	/* process-locks endpoints;
	hApiVersions["v1"]["process-locks"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"]["process-locks"]["process"] := "API_ProcessLocks_v1.Process";
	
	/* user-management endpoints;
	hApiVersions["v1"]["user-management"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"]["user-management"]["user-session"] := "API_UserManagement_v1.UserSession";
	
	hApiVersions["v1"]["sqs"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"]["sqs"]["message-queue"] := "API_SQS_v1.message";

	hApiVersions["v1"]["load"] := LimsNetConnect("", "System.Collections.Hashtable");
	hApiVersions["v1"]["load"]["encrypt"] := "API_Load_v1.encrypt";
	hApiVersions["v1"]["load"]["origrec"] := "API_Load_v1.origrec";

	:RETURN hApiVersions;
:ENDPROC;


:PROCEDURE Route;
	:PARAMETERS routingInfo;
	
	/* 	routingInfo
			.Version : string - e.g. "v1"
			.Service : string - e.g. "folderlogin"
			.Entity : string - e.g. "sample";
	
	:DECLARE hRoutesDef, sVersion, sService, sEntity;
	
	hRoutesDef := Me:getRoutes();
	
	/* remove case route;
	sVersion := Lower(routingInfo:Version);
	sService := Lower(routingInfo:Service);
	sEntity := Lower(routingInfo:Entity);
	
	:IF !Empty(hRoutesDef[sVersion]);
		:IF !Empty(hRoutesDef[sVersion][sService]);
			:RETURN hRoutesDef[sVersion][sService][sEntity];
		:ENDIF;
	:ENDIF;
	
	:RETURN "";
	
:ENDPROC;

When you need to add new routes, all you do is add new lines to the getRoutes method, the logic in the Route method is static and shouldn’t change. Then, you create the corresponding categories and scripts to actually run your logic, and you’re set.

Of course, you can build your own mechanism – it is by no mean the best one; but I do find it to be easier to manage than STARLIMS’ suggestion.

Now, I know: you might be tempted to write a generic data-driven routing. I was tempted to do it. In the end, it is a balance between convenience and security. If you let it be data-driven, you loose control on what can be routed. Someone may modify the route to, let’s say, get result, to instead return all user information, and you wouldn’t know. If it’s in the code, then you’ll know. So – although it is not as convenient, don’t get your routes handled by the database. It would also add extra load on the database. So – no good reasons other than convenience, really.

2- properly document your APIs. Heck, document your APIs before you implement them! I recommend https://swagger.io/ to generate some .yaml files. Trust me: whoever will be consuming your API will thank you!

All in all, I think the STARLIMS REST API really brings the system to an all new level. Theoretically, one could build a full UI stack using React or Angular and just consume the API to run the system on a new front end.

Or one could expose data endpoints for pipelines to maintain a data mart.

Or anything. At this point, your creativity is the limiting factor. Do you have great ideas for use cases?