APSystems Open API (rest)

Hello

I'm not that good at programming so I'm askin help here. I'd like to read APSystems API using nodered and that requires calculating a signature for REST API polls.

How would one do this in a nodered function node:

HmacSHA256
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte[] appSecretBytes = appSecret.getBytes(Charset.forName("UTF-8"));
hmacSha256.init(new SecretKeySpec(appSecretBytes, 0, appSecretBytes.length, "HmacSHA256"));
byte[] md5Result = hmacSha256.doFinal(stringToSign.getBytes(Charset.forName("UTF-8")));
String signature = Base64.getEncoder().encodeToString(md5Result);

Or even better if someone already has created Nodes for querrying APSystems.

Manual can be found here: https://file.apsystemsema.com:8083/apsystems/resource/openapi/Apsystems_OpenAPI_User_Manual_End_User_EN.pdf

In a function node, you could use something like this:
This was created using AI, we cannot test it as we don't have your details (nor should we).

In the setup tab of the function node, add a 'module': crypto -> import as crypto

// Replace these with your actual credentials and endpoint
const APP_ID = 'your-app-id';
const APP_SECRET = 'your-app-secret';
const BASE_URL = 'https://api.apsystemsema.com:9282';
const REQUEST_PATH = '/your/api/endpoint'; // e.g., '/v1/data'
const HTTP_METHOD = 'GET'; // or 'POST', 'DELETE', etc.

// Step 1: Prepare headers
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString('hex'); // 32-char hex string
const signatureMethod = 'HmacSHA256'; // or 'HmacSHA1'

// Step 2: Construct stringToSign
const stringToSign = [
    timestamp,
    nonce,
    APP_ID,
    REQUEST_PATH, 
    HTTP_METHOD,
    signatureMethod,
].join('/');

// Step 3: Calculate signature
function calculateSignature(secret, stringToSign, method) {
    const algo = method === 'HmacSHA1' ? 'sha1' : 'sha256';
    const hmac = crypto.createHmac(algo, secret);
    hmac.update(stringToSign, 'utf8');
    return hmac.digest('base64');
}

const signature = calculateSignature(APP_SECRET, stringToSign, signatureMethod);

// Step 4: Set headers
const headers = {
    'X-CA-AppId': APP_ID,
    'X-CA-Timestamp': timestamp,
    'X-CA-Nonce': nonce,
    'X-CA-Signature-Method': signatureMethod,
    'X-CA-Signature': signature
};

msg.url = `${BASE_URL}${REQUEST_PATH}`
msg.headers = headers
msg.method = HTTP_METHOD
return msg;

output

example flow

[{"id":"bbec24edee469140","type":"inject","z":"99edd4530f071b75","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":260,"wires":[["0548990c537a2ae2"]]},{"id":"0548990c537a2ae2","type":"function","z":"99edd4530f071b75","name":"function 8","func":"\n// Replace these with your actual credentials and endpoint\nconst APP_ID = 'your-app-id';\nconst APP_SECRET = 'your-app-secret';\nconst BASE_URL = 'https://api.apsystemsema.com:9282';\nconst REQUEST_PATH = '/your/api/endpoint'; // e.g., '/v1/data'\nconst HTTP_METHOD = 'GET'; // or 'POST', 'DELETE', etc.\n\n// Step 1: Prepare headers\nconst timestamp = Date.now().toString();\nconst nonce = crypto.randomBytes(16).toString('hex'); // 32-char hex string\nconst signatureMethod = 'HmacSHA256'; // or 'HmacSHA1'\n\n// Step 2: Construct stringToSign\nconst stringToSign = [\n    timestamp,\n    nonce,\n    APP_ID,\n    REQUEST_PATH, \n    HTTP_METHOD,\n    signatureMethod,\n].join('/');\n\n// Step 3: Calculate signature\nfunction calculateSignature(secret, stringToSign, method) {\n    const algo = method === 'HmacSHA1' ? 'sha1' : 'sha256';\n    const hmac = crypto.createHmac(algo, secret);\n    hmac.update(stringToSign, 'utf8');\n    return hmac.digest('base64');\n}\n\nconst signature = calculateSignature(APP_SECRET, stringToSign, signatureMethod);\n\n// Step 4: Set headers\nconst headers = {\n    'X-CA-AppId': APP_ID,\n    'X-CA-Timestamp': timestamp,\n    'X-CA-Nonce': nonce,\n    'X-CA-Signature-Method': signatureMethod,\n    'X-CA-Signature': signature\n};\n\nmsg.url = `${BASE_URL}${REQUEST_PATH}`\nmsg.headers = headers\nmsg.method = HTTP_METHOD \n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"crypto","module":"crypto"}],"x":400,"y":260,"wires":[["28445f3da922c7fc"]]},{"id":"28445f3da922c7fc","type":"debug","z":"99edd4530f071b75","name":"debug 33","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":600,"y":260,"wires":[]}]

When providing the details, you should be able to pass this through a http request node (set the method to msg.method)

Wow. That looks really good. Tried it out and it gives me code 4000 which is according to manual a "Request parameter exception".

Hard to troubleshoot when there is no more information about what parameter is wrong...

It sounds like it is working, except for the parameter (ie. REQUEST_PATH), what are you trying to request ?

const SID = 'censored';
const REQUEST_PATH = '/user/api/v2/systems/details/'+SID;

{"_msgid":"1661bf674f9f5590","payload":{"code":4000},"url":"https://api.apsystemsema.com:9282/user/api/v2/systems/details/################","headers":{"date":"Sat, 07 Jun 2025 11:18:52 GMT","content-type":"application/json","transfer-encoding":"chunked","connection":"keep-alive","server":"openresty/1.21.4.1","vary":"Accept-Encoding","x-node-red-request-node":"0f006774"},"statusCode":200,"responseUrl":"https://api.apsystemsema.com:9282/user/api/v2/systems/details/################","redirectList":[],"retry":0}

I can also see from the API webpage that I have logged in several times with API. So it is almost working.

I would suggest to send the code to them as an example and request a sample how to authenticate with nodejs because their documentation is not clear enough.

Ok, have to try. APSystems is shitty in helping end customers. You really need to be a installer/company to contact them. Problem with this is that none of the installers are coders.

I was thinking could the error code 4000 have something to do with md5? In the manual there is some mentions to md5 but in your code there is none of that.

Got it working. Documentation was bad for "RequestPath (The last name of the path)".

This works:

// Replace these with your actual credentials and endpoint
const SID = '0000000000000000';
const APP_ID = ‘00000000000000000000000000000’;
const APP_SECRET = ‘000000000000’;
const BASE_URL = 'https://api.apsystemsema.com:9282';
const REQUEST_PATH = '/user/api/v2/systems/details/'+SID;
const HTTP_METHOD = 'GET'; // 'GET' or 'POST' or 'DELETE'

var urlSegments = REQUEST_PATH.split("/");
var lastSegment = urlSegments[urlSegments.length - 1];

// Step 1: Prepare headers
const timestamp = Date.now().toString();
const nonce = crypto.randomBytes(16).toString('hex'); // 32-char hex string
const signatureMethod = 'HmacSHA256'; // or 'HmacSHA1'

// Step 2: Construct stringToSign
const stringToSign = [
    timestamp,
    nonce,
    APP_ID,
    lastSegment,
    HTTP_METHOD,
    signatureMethod,
].join('/');

// Step 3: Calculate signature
function calculateSignature(secret, stringToSign, method) {
    const algo = method === 'HmacSHA1' ? 'sha1' : 'sha256';
    const hmac = crypto.createHmac(algo, secret);
    hmac.update(stringToSign, 'utf8');
    return hmac.digest('base64');
}

const signature = calculateSignature(APP_SECRET, stringToSign, signatureMethod);

// Step 4: Set headers
const headers = {
    'X-CA-AppId': APP_ID,
    'X-CA-Timestamp': timestamp,
    'X-CA-Nonce': nonce,
    'X-CA-Signature-Method': signatureMethod,
    'X-CA-Signature': signature
};

msg.url = `${BASE_URL}${REQUEST_PATH}`
msg.headers = headers
msg.payload = stringToSign;
return msg;

3 Likes

Nice! Indeed that ‘last part’ of the request path was confusing in their doc. Enjoy