• Migration Topics
  • Integration
  • Concepts
  • Best Practices
  • Api
    • Changelog
    • HIRO API Overview
    • HIRO Audit API
    • HIRO Graph - Gremlin Query
    • HIRO Graph Action API
    • HIRO Graph Auth API
    • HIRO Graph List API
    • HIRO Graph WebSocket API
    • Refresh Token

Overview

This document will give you an overview about the HIRO Graph Developer WS API.

Concepts

Please be familiar with the HIRO Graph and the Websocket Protocol.

Requirements of Namespace, IDs and Attributes

  1. All Requirements of HIRO Graph apply.

WS API

In order to use the API you need to register an application, please drop us a line at support@hiro.arago.co.
  1. All initial connect requests must contain a valid access token _TOKEN. Request a valid access token from the HIRO IAM server.

  2. All requests will be made against a base $url (e.g. core.arago.co).

  3. All further examples assume wss:// as the protocol.

WS Event Streaming API (events-1.0.0)

The Streaming endpoint is located at /api/events-ws/.

The standard steps to follow are:

  1. Connect

  2. Subscribe to scope (if allscopes set to false)

  3. Register a filter

  4. Receive data

Example
const ws = new WebSocket(`wss://${url}/api/events-ws/6/?groupId=&offset=`, ['events-1.0.0', 'token-' + token]);
Table 1. Table Parameters
Name Description Default

groupId

subscribe to the same event stream by opening multiple connections with the same groupId. The stream will be partitioned amongst all connections. If connection is lost, the groupId will allow you to receive any missed events.

''

offset

smallest (start from the last event received by this group) or largest (start by events that come in after the connection).

''

delta

Only receive the changes to a vertex, not the whole vertex. Each a attribute in the event body is prefixed with +, -, = (added, removed, changed).

false

allscopes

By default, events from all scopes are subscribed to. Setting this to false, requires use of the subscribe message to subscribe to specific scopes. See message types below.

true

Requests

Example: Updating a token on an already established connection
const msg = {
  'type': 'token',
  'args': {
   _TOKEN: 'new token'
  }
};
ws.send(JSON.stringify(msg));
Example: Subscribe to events from a given scope
const msg = {
  'type': 'subscribe',
  'id': scope_id // The ogit/_id of the ogit/DataScope (i.e. the scope of your instance) you want to subscribe to
};
ws.send(JSON.stringify(msg));
Example: Register a Filter
const msg = {
  'type': 'register',
  'args': {
   'filter-id': 'unique filter id for this websocket',
   'filter-type': 'jfilter',
   'filter-content': '(element.ogit/_type = ogit/Question)'
  }
};
ws.send(JSON.stringify(msg));
Example: Unregister a Filter
const msg = {
  'type': 'unregister',
  'args': {
   'filter-id': 'unique filter id for this websocket'
  }
};
ws.send(JSON.stringify(msg));
Example: Unregister all Filters
const msg = {
  'type': 'clear',
  'args': {}
};
ws.send(JSON.stringify(msg));

Responses

Events contain the originator of the event, the action taken, and the contents.

Standard events

By default, the eventstream sends the entire body of the affected vertex.

Example: An Event
{
  id: string; // ogit/_id from body
  body: {
   /* properties like ogit/_id */
   "ogit/name": "My name"
  },
  metadata: {
    "ogit/_modified-by": string;
    "ogit/_modified-by-app": string;
    "ogit/_modified-on": number;
  };
  nanotime: number;
  timestamp: number;
  type:  "CREATE" | "REPLACE" | "UPDATE" | "CONNECT" | "DISCONNECT" | "DELETE" | "WRITE_TIMESERIES";
}
Delta events

Delta events only contain the changes on the node. Each attribute is prefixed by +, -, =.

Example: An Delta Event
{
  id: string; // ogit/_id from body
  body: {
   /* properties like ogit/_id prefixed by change */
   "+ogit/firstName": "My name" // Added
   "-ogit/lastName": "My name" // Removed
   "=ogit/email": "My name" // Changed
  },
  metadata: {
    "ogit/_modified-by": string;
    "ogit/_modified-by-app": string;
    "ogit/_modified-on": number;
  };
  nanotime: number;
  timestamp: number;
  type:  "CREATE" | "REPLACE" | "UPDATE" | "CONNECT" | "DISCONNECT" | "DELETE" | "WRITE_TIMESERIES";
}

Filters

In order to only receive certain events, filters can be registered. The filterType is currently only jfilter.

Syntax
Table 2. Table Filter Operators
Operator Description Supported types Filter example

=

equals to

String, Number

(firstname = John)

~

differs from

String, Number

(name ~ Smith)

>

more than

Number

(height > 1.6)

>=

more or equals

Number

(height >= 1.6)

<

less than

Number

(age < 20)

less or equals

Number

(age ⇐ 20)

!

not

Filter

!(age<10)

&

and

Filters

&(name=Doe)(firstname=John)

|

or

Filters

|(age<10)(male=true)

wildcards

matches all

String

&(firstname=J*)(name=Do?)

Example: A Filter
{
  'filter-content': '&(element.ogit/_type=ogit/Question)(action=C*)'
}

WS Graph API (graph-2.0.0)

The WS Graph API permits to do REST primitives over WS. The WS Graph API endpoint is located at /api/graph-ws/.

Blobs can be fetched via this api, but they can only be stored via REST.
Example
const ws = new WebSocket(`wss://${url}/api/graph-ws/6/`, ['graph-2.0.0', 'token-' + token]);
ws.addEventListener("open", function() {
  if (ws.protocol !== "graph-2.0.0") {
    throw new Error("Expecting WebSocket protocol 'graph-2.0.0', got " + ws.protocol);
  }
  //do something with connection
});

Overview

Websocket API requests are JSON, and have the following structure:

Example
const request =
{
  'id': 'mandatory id for matching the reply',
  'type': 'the type of the request, e.g. get, create, replace, connect, ...',
  '_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
  'headers':
  {
    // headers for the request
  },
  'body':
  {
    // body for the request
  }
};
ws.send(JSON.stringify(request));

In order to do correct multiplexing in the websocket stream, all responses will be wrapped by an envelope. A client must buffer all messages until the flag more is false in an envelope.

Response Envelope:

{
  "id": "id of the request",
  "more": true|false // `false` if this is the last message for the response
  "multi": true|false // `true` if this response is fragmented, the stream of messages represents an array as the final result
  "body": []|{}|... // for responses with `multi=true` the body should be put to an array [] and when `more=false` the result should be the array with the assembled parts
}

In order to process these envelope which arrive intermixed with other response envelopes, one must buffer the envelopes unti the last flag is true.

const requests = {}; // holds all responses

ws.onmessage = function(msg)
{
  const payload = JSON.parse(what);

  const request = requests[payload.id];
  if (!request) throw new Error("request could not be found " + req.id);

  if (payload.error)
  {
    // an error must always be delivered
    request.cb(payload);
    delete(requests[payload.id]);
  } else if (!payload.multi && payload.more) {
    throw new Error("non-multi messages cannot be fragmented");
  } else if (!payload.multi) {
    // thats a single response message
    request.cb(payload);
    delete(requests[payload.id]);
  } else {
    // thats a fragmented response, buffer up everything until payload.more = false
    if (payload.body !== null) request.buf.push(payload.body);request.buf.push(payload.body);

    if (!payload.more)
    {
      request.cb(request.buf);
      delete(requests[payload.id]);
    }
  };
);

Individual Requests

Get an Entity
{
  "id":   "request id",
  "type": "get",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Create an Entity
{
  "id":   "request id",
  "type": "create",
  /* other optional properties */
  "headers":
  {
    "ogit/_type": "type of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Update an Entity
{
  "id":   "request id",
  "type": "update",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Replace an Entity
{
  "id":   "request id",
  "type": "replace",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node",
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Replace an Entity/Verb
{
  "id":   "request id",
  "type": "delete",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node/edge"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Connect two Entities
{
  "id":   "request id",
  "type": "connect",
  /* other optional properties */
  "headers":
  {
    "ogit/_type": "type of the verb"
  },
  "body":
  {
    "out": "id of the outgoing node",
    "in": "id of the ingoing node"
  }
}
Query
{
  "id":   "request id",
  "type": "query",
  /* other optional properties */
  "headers":
  {
    "type": "type of the query, e.g. vertices"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Write Timeseries Values
{
  "id":   "request id",
  "type": "writets",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Fetch Timeseries Values
{
  "id":   "request id",
  "type": "streamts",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Fetch History of an Entity
{
  "id":   "request id",
  "type": "history",
  /* other optional properties */
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Me
{
  "id":   "request id",
  "type": "me",
  '_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
  "headers":
  {
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
Fetch blob content
{
  "id":   "request id",
  "type": "getcontent",
  '_TOKEN': 'optional, if none specified, the one from the establishing the websocket will be used',
  "headers":
  {
    "ogit/_id": "id of the node"
  },
  "body":
  {
    // all parameters for the corresponding REST request are available here
  }
}
In order to store blob content, the REST API must be used.

The responses for receiving blob content are chunked envelopes looking like this, and the client needs to assemble these:

{
  "id":   "request id",
  "multi": true,
  "last": true|false
  "body":
  {
    "data": "chunk of blob data",
    "encoding": "base64" // base64 is currently the only encoding possible
  }
}

Error Codes

  1. All error codes of HIRO Graph API apply.

  2. All error codes of Websocket Protocol apply.

On Websocket close message HIRO Graph error codes are not propagated and only Websocket Protocol codes are returned. If exists error from close message content should be used to recognize and implement handling. Example error response message:
{
  "error": {"message": "authentication required"}
}