npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@hahnpro/hpc-api

v2025.12.1

Published

Module for easy access to the HahnPRO Cloud API

Readme

Hahn PRO Cloud API

1. Fundamentals

The Hahn PRO Cloud API is a RESTful API, therefore the communication consists of HTTP-calls. The services provided are independent microservices, each with their own subpaths.

In this document there are code-samples for the most common API use-cases in typescript and python. HahnPRO provides an SDK, which simplifies the communication in typescript. Currently, there is no SDK for python.

2. Authentication

To use the API you will have to authenticate yourself with every request. For this a Authentication header with a JWT-Token is needed.

To get the JWT-Token, you have to use your API-Username and Password to get the token from Keycloak. The request to keycloak needs to be a POST request with {'grant_type': 'client_credentials'} as the body.

The token is valid for a set amount of time, after which you will need to reauthenticate. In the expires_in response field you can get the time for which the token will be valid.

In the following sample-code the user gets asked for their password, which then gets used to ask Keycloak for the JWT-Token. The token then gets used in the Authentication header.

You will need to set the variables API_BASE_URL, AUTH_BASE_URL, AUTH_REALM and API_USER. The AUTH_BASE_URL will often be the same as the API_BASE_URL.

import requests
import getpass

# gets an access token
keycloak_url = AUTH_BASE_URL + '/auth/realms/' + AUTH_REALM + '/protocol/openid-connect/token'
headers = {'Content-Type':'application/x-www-form-urlencoded'}
apiPw = getpass.getpass('Password for ' + API_USER)
auth=(API_USER, apiPw)
data = {'grant_type': 'client_credentials'}
res = requests.post(keycloak_url, auth=auth, headers=headers, data=data )
apiPw = '';
token = res.json()['access_token']
print('token: ' +  token[:5] + '...' + str(len(token)))
headers = {'Authorization':'Bearer ' + token,
           'Content-Type': 'application/json'}

The headers need to be sent with every request to the API.

The Hahn-PRO Flow-SDK does most of the work in getting and using the token. You only have to set the API_BASE_URL ( defaults to https://testing.hahnpro.com), AUTH_BASE_URL (defaults to API_BASE_URL), AUTH_REALM (defaults to hpc), API_USER and AUTH_SECRET environment variables;

import { API } from '@hahnpro/flow-sdk';
// or by using dotenv
import * as dotenv from 'dotenv';

// explicitly set the envs
process.env.API_BASE_URL = '*your API URL*';
process.env.AUTH_BASE_URL = '*URL of the Keycloak instance the authenticate with*'; // only needed if distinct from API_BASE_URL
process.env.AUTH_REALM = '*the Keycloak Realm*';
process.env.API_USER = '*your username*';
process.env.AUTH_SECRET = '*your secret*';

dotenv.config();

const api = new API();

The JWT-Token is automatically requested and used with every request you send, using the api object.

3. Basic Example Use-Cases

3.1. General

3.1.1 Populate

When you get assets from the API, the Asset-Type and the parent-Asset are given as IDs. You can get the Asset-Type and Parent objects by sending the populate queryparameter.

...?populate=type,parent

3.1.2 Filter

To get filtered results a filter string has to be provided:

| Attribute | filter string | | --------- | --------------- | | tags | tags=@tag1 tag2 | | type | type==type | | parent | parent==1234 |

Multiple filters can be concatenated with the ; symbol.

...?filter=tags=@test;parent==1234

3.1.3 Read/Read-Write Permissions

Every single object from the API has the fields readPermissions and readWritePermissions. These determine which objects can be read or written. When writing a new object to the API, these fields have to be sent.

You can only set the roles that you have, e.g. when you don´t have the role test you can´t set this role in the readPermissions or readWritePermissions arrays.

To see the roles you have, you can check the realm_access.roles field of the JWT-Token.

Get user roles from JWT-Token.

import jwt

def getUserRoles(token):
    algorithm = jwt.get_unverified_header(token)['alg']
    data = jwt.decode(token, algorithms=algorithm, audience='account', options={"verify_signature": False})
    return data['realm_access']['roles']

This example uses the pyjwt library.

Get user roles from JWT-Token.

const roles = await api.users.getCurrentUserRoles();

3.2. Assets

In this example some Assets will be fetched and modified.

API_AM_PATH = '/api/assets'

Get all assets:

res = requests.get(API_BASE_URL + API_AM_PATH, headers=headers)
assets = res.json()['docs']

Get a single Asset:

assetId = '1234'
res = requests.get(API_BASE_URL + API_AM_PATH + '/' + assetId, headers=headers)
asset = res.json()

Get all Assets with tag:

filterString = '?filter=tags=@test'
res = requests.get(API_BASE_URL + API_AM_PATH + filterString, headers=headers)
assets = res.json()['docs']

Populate Asset-type and Parent-Asset:

populateString = '?populate=type,parent'
res = requests.get(API_BASE_URL + API_AM_PATH + populateString, headers=headers)
print(res.json()['docs'])

Create a new Asset:

asset = {
  name: 'newAsset',
  type: '1234',
  readPermissions: ['test'],
  readWritePermissions: ['test', 'admin']
}

res = requests.post(API_BASE_URL + API_AM_PATH, data=json.dumps(asset), headers=headers)

Update existing asset:

import json

# get asset
assetId = '1234'
res = requests.get(API_BASE_URL + API_AM_PATH + '/' + assetId, headers=headers)
asset = res.json()

# update asset
asset['tags'] = ['qwertz', 'test']

# save aset
res = requests.put(API_BASE_URL + API_AM_PATH + '/' + assetId, data=json.dumps(asset), headers=headers)

Get all assets:

// get a paginated list of all Assets
const all = await api.assets.getMany();
const assets = all.docs; // asset-objects are contained in the docs array

Get a single Asset:

const asset = await api.assets.getOne('*ID of asset*');

Get all Assets with tag:

const filtered = await api.assets.getManyFiltered({ tags: ['test'] });
const assets = filtered.docs;

Populate Asset-type and Parent-Asset:

const many = await api.assets.getMany({ populate: 'type,parent' });
const assets = many.docs;

Create a new Asset:

const asset: Asset = {
  name: 'newAsset',
  type: '1234',
  readPermissions: ['test'],
  readWritePermissions: ['test', 'admin'],
};

await api.assets.addOne(asset);

Update existing Asset:

const asset = await api.assets.getOne('1234');
// modify
asset.tags.push('newTag');

await api.assets.updateOne('1234', asset);

3.3. Content

The Content object from the api contains all the metadata of the content-file, but not the actual file. The file has to be downloaded separately.

To upload a new File you have to send it as multipart/form-data in the file field.

API_CM_PATH = '/api/contents'

Get all Content:

res = requests.get(API_BASE_URL + API_CM_PATH, headers=headers)
contents = res.json()['docs']

Download Content:

contentId = '1234'
res = requests.get(API_BASE_URL + API_CM_PATH + '/' + contentId + '/download', headers=headers)
blob = res.content

Download Content attached to an Asset:

ASSET_WITH_CONTENT = '1234'
res = requests.get(API_BASE_URL + API_AM_PATH +'/' + ASSET_WITH_CONTENT, headers=headers)
attachments = res.json()['attachments']

res = requests.get(API_BASE_URL + API_CM_PATH + '/' + attachments[0] + '/download', headers=headers)
blob = res.content

Upload new Content:

If you are using the same headers set from the authentication example, you have to the Content-Type header.

del headers['Content-Type']
files = {'file': open('somefile.txt', 'rb')}
payload={
    'readPermissions': 'test',
    'readWritePermissions': 'test'
}

res = requests.post(API_BASE_URL + API_CM_PATH, files=files, data=payload, headers=headers)

Attach Content to Asset:

files = {'file': open('somefile.txt', 'rb')}
payload={
    'readPermissions': 'test',
    'readWritePermissions': 'test'
}

res = requests.post(API_BASE_URL + API_AM_PATH + '/1234/attachment', data=payload, files=files, headers=headers)

Get all Contents:

const many = await api.contents.getMany();
const contents = many.docs;

Download Content:

const content = await api.contents.getOne('1234');
const file = await api.contents.download(content.id);

Download Content attached to an Asset:

const asset = await api.contents.getOne('1234');
const file = await api.contents.download(asset.attachments[0]);

Upload new Content:

import FormData from 'form-data';

const form = new FormData();
form.append('file', fs.createReadStream('/foo/bar.jpg'));
form.append('readPermission', 'test');
form.append('readWritePermission', 'test');

await api.contents.upload(form);

Attach Content to Asset:

import FormData from 'form-data';

const form = new FormData();
form.append('file', fs.createReadStream('/foo/bar.jpg'));
form.append('readPermission', 'test');
form.append('readWritePermission', 'test');

await api.assets.addAttachment('1234', form);

3.4. Timeseries

A TimeSeries object contains all the metadata of the timeseries but no values. The values can be downloaded separately.

Get timeseries:

tsId = '1234'
res = requests.get(API_BASE_URL + API_TSM_PATH + '/' + tsId, headers=headers)
ts = res.json()

# download the first 1000 values
res = requests.get(API_BASE_URL + API_TSM_PATH + '/' + tsId + '/' + '0' + '?limit=1000', headers=headers)
values = res.json()

Create new empty timeseries:

ts = {
    'name': 'newTS,
    'readPermissions': ['test'],
    'readWritePermissions': ['test']
}

res = requests.post(API_BASE_URL, data=json.dumps(ts), headers=headers)

Add value to Timeseries:

data = { '1234': 5, '1235': 6}; // { *timestamp* : *value*}
# multiple values
data1 = { '1234': { val1: 1, val2: 2} };

requests.post(API_BASE_URL + API_TSM_PATH + '/1234', data=json.dumps(data), headers=headers)

Get Timeseries:

const ts = await api.timeSeries.getOne('1234');

const from = 0; // timestamp
const to = Date.now(); // timestamp (optional)
const group = '12h'; // "10s", "1m", "5m", "15m", "30m", "1h", "3h", "6h", "12h", "1d", "7d" (optional)
const values = await api.timeSeries.getValuesOfPeriod(tsId, from, to, group);

Create new empty timeseries:

const ts: TimeSeries = {
  name: 'newTS',
  readPermissions: ['test'],
  readWritePermissions: ['test', 'admin'],
  minDate: Date.now,
  maxBucketTimeRange: 86400000,
};

await api.timeSeries.addOne(ts);

Add value to Timeseries:

const value = { '1234': 5, '1235': 6 }; // { *timestamp* : *value*}
// multiple values
const value1 = { '1234': { val1: 1, val2: 2 } };

await api.timeSeries.addValue('1234', value);