Bulk API
Available for 8.4 Control Center versions and above.
HYPR Bulk API endpoints serve as a direct interaction with fields which are present in the database tables. The purpose of this endpoint is to provide a way to have READ access to the raw data in the database per table. This document will go over the outline, endpoints, and some examples.
The Bulk API calls are located in our Postman Collection.
API Call Overview
Global Endpoints
GET /cc/api/bulk/introspect
GET /cc/api/bulk/introspect/{entityName}
POST /cc/api/bulk/fetch
Relaying Party Application (RPApp) Endpoints
GET /rp/api/bulk/{rpAppId}/introspect
GET /rp/api/bulk/{rpAppId}/introspect/{entity}
POST /rp/api/bulk/{rpAppId}/fetch
HYPR API's authorization method is Bearer Token. This is a read-only endpoint; as such, Permission Type should be set to Reporting when creating the API Token in Control Center.
/rp/api/bulk/{rpAppId}/introspect and /rp/api/bulk/{rpAppId}/introspect/{entity} and POST /rp/api/bulk/{rpAppId}/fetch will use the respective RPApp API Token. There are limitation on what's available for the RPApp scoped calls. While all RPApp scoped entities will be in global calls, not all globally scoped entities will be in RPApp scoped calls. This is done intentionally in order to limit the amount of information available to someone that has RPApp API Token versus CC Admin API Token. For example; devices entity does not have an RPAppID column, hence it's not available in the RPApp scoped calls.
Introspect
Introspect API helps figure out what is available for query:
-
Entities available
-
Fields inside those entities
-
Operations possible on those fields
GET /cc/api/bulk/introspect
The global introspect call will return all entities and the information around them, as outlined above:
GET /cc/api/bulk/introspect/{entityName}
This call is used to filter only towards the entity of interest; otherwise it returns the same result as /introspect.
/rp/api/bulk/{rpAppId}/introspect and /rp/api/bulk/{rpAppId}/introspect/{entity} serve the same purpose with added RPAppID filtering.
A typical result will look something like this (condensed to save space):
{"status": "ok",
"entities":[
{"name": "featureflags", "description": "feature flags"},
{"name": "registrations", "description": "combination of user, device, machine"},
{"name": "users", "description": "registered users"},
{"name": "devices", "description": "mobile/security keys"}],
"fields":{
"featureflags":[
{"name": "featureName", "description": "name of feature", "fieldtype": "string", "operations": "EQ,LIKE"},
{"name": "featureEnabled", "description": "is the feature enabled", "fieldtype": "boolean", "operations": "EQ"},
{"name": "description", "description": "description of feature", "fieldtype": "string", "operations": "EQ,LIKE"},
{"name": "rpAppId", "description": "specific rp app feature is on for", "fieldtype": "string", "operations": "EQ,LIKE"}],
"registrations":[{"name": "rpAppId", "description": "rp app id", "fieldtype": "string", "operations": "EQ,LIKE"},...]}}
Fetch
The fetch API lets you action the information from Introspect and obtain the needed information based on filters. We will describe the bulk of the Request Body and filters, with some examples.
POST /cc/api/bulk/fetch
POST /rp/api/bulk/{rpAppId}/fetch serves the same purpose with added RPAppID filtering.
The Request Body specifies what you want to fetch (e.g., a single table), what fields you want, what filters you want, and some pagination information to help break up lengthy results sets. Here is a sample Request Body JSON:
{"target":
{"subject": "devices",
"fields":["deviceId", "protocolVersion", "type"],
"filter":{"args":[{"filter":{"op": "EQ", "fieldRef": "type", "literal": "ANDROID"}}]}},
"pagination":{"page":0, "rows":100}}
The filter supports AND, OR , and NOT as combiners.
It supports GT, LT, EQ, LIKE as relational operators.
To know what to fetch, subject and fields are required; however, filter and pagination are nullable. A null value in filter returns all records found in a given subject (entity). If you omit pagination info, only a row count is returned:
{"status": "ok", "count": 1630, "results": []}
countThere is no inherent pagination built into the endpoint. You may find count useful for your own pagination efforts should this be used in a script. You may get number of pages by dividing the count by number of rows you wish to show. If there are no results for a given page, it returns an empty array, so you may also increment the page until you get an empty results array: {"status": "ok", "count":null, "cost":220, "results":[]}
Fetch Examples
Fetch endpoint's payload is very dynamic and flexible. To demonstrate some of the possibilities we will look through examples of increasing complexity:
General Fetch
{"target":
{"subject": "devices",
"fields":["deviceId", "protocolVersion", "type"],
"ordering":{"ascending":true, "fieldRef": "type"},
"filter":null},
"pagination":{"page":0, "rows":100}}
In this example we are querying the devices entity for deviceId, protocolVersion, and type, fields. We want ordering to be ascending on the type field without any filters, and 100 rows.
Fetch with a Filter
{"target":
{"subject": "devices",
"fields":["deviceId", "modelNumber", "type"],
"filter":{
"args":[{"filter":{"op": "EQ", "fieldRef": "type", "literal": "ANDROID"}}]}},
"pagination":{"page": 0, "rows": 100}}
In this example we are querying the devices entity for deviceId, modelNumber, and type fields. We are introducing args and filter here. We are looking for field of type equal to ANDROID. We do not have any ordering and are looking for 100 rows.
Fetch with a Filter and a Top-level Combiner
{"target":
{"ordering":{"fieldRef": "type", "ascending": true},
"subject": "devices",
"fields":["deviceId", "modelNumber", "type"],
"filter": {
"op": "OR",
"args":[
{"filter":{"op": "EQ", "fieldRef": "type", "literal": "IOS"}},
{"filter":{"op": "EQ", "fieldRef": "type", "literal": "ANDROID"}}]}},
"pagination":{"page": 0, "rows": 100}}
In this example we are querying the devices entity for deviceId, modelNumber, and type fields. We are ordering by field type ascending. We are introducing a top-level combiner, OR. This will affect subsequent children filters inside the args array. Here we are looking for type fields which equal either IOS or ANDROID.
There is an arbitrary number of filters to go with combiners. One of the use cases here would've been searching for specific number of users or devices, which would lead you to use an OR combiner with x number of filters inside an args array.
Fetch Filter with Multiple Combiners and Relational Operator
{
"target": {
"subject": "sessions",
"fields": ["deviceId", "machineId", "creationTime"],
"filter": {
"op": "OR",
"args": [
{
"relop": {
"op": "AND",
"args": [
{"filter": {"op": "EQ", "fieldRef": "deviceId", "literal": "DevIdiatgb75dr5hk8d9gh318ktjsji"}},
{"filter": {"op": "EQ", "fieldRef": "machineId", "literal": "3164a8e121556e2a6e81a4479a46fae8cf828bc94723a674ae1d3af0f52c33f4"}}]}},
{
"relop": {
"op": "AND",
"args": [
{"filter": {"op": "EQ", "fieldRef": "deviceId", "literal": "DevIdiatgb75dr5hk8d9gh318ktjsji"}},
{"filter": {"op": "EQ", "fieldRef": "machineId", "literal": "79a6733540b82a47a2e81dc2b2cf1083679bd35df75b0aef3bb1bfe306b1d9a0"}}]}}
]
}
},
"pagination": {"page": 0, "rows": 5}}
In this example we are querying the sessions entity for deviceId, machineId, and creationTime fields for specific information about two created sessions. This time we are specifying a relop key, since we have more than one args array for comparison. Notice how the idiom of having a top-level op inside a filter map still stands. relop has the same construction as filter. On the top level we are looking for an OR operation on two relational operations of AND. We are looking for a deviceId = DevIdiatgb75dr5hk8d9gh318ktjsji and a machineId = 3164a8e121556e2a6e81a4479a46fae8cf828bc94723a674ae1d3af0f52c33f4 OR a deviceId = DevIdiatgb75dr5hk8d9gh318ktjsji and a machineId = 79a6733540b82a47a2e81dc2b2cf1083679bd35df75b0aef3bb1bfe306b1d9a0. We do not have any ordering and, since we expect two results, rows is 5 in case there are unwanted results.
To help with visualization for this particular payload, here is an example result:
{
"status": "ok",
"count": null,
"cost": 600,
"results": [
{"deviceId": "DevIdiatgb75dr5hk8d9gh318ktjsji", "machineId": "79a6733540b82a47a2e81dc2b2cf1083679bd35df75b0aef3bb1bfe306b1d9a0", "creationTime": 1698441323000},
{"deviceId": "DevIdiatgb75dr5hk8d9gh318ktjsji", "machineId": "3164a8e121556e2a6e81a4479a46fae8cf828bc94723a674ae1d3af0f52c33f4", "creationTime": 1704833005000}]}