The Journey Hub API allows OEM organizations to automate the management of Organizations, Users and Projects.
Hub API Authentication Process
- Generate a secret key (you must be a Journey Hub owner) by going to the admin section, clicking users and selecting yourself from the left panel. Then you will see a button to "generate key".
- On the same page, copy your user id.
- Create the hash (see instructions below)
- Create a 'POST' request to the /token route (see 'Request Object' format below)
- The response returns a token and an expiration time. You can now include the token in the headers in order to access the user, org, and project routes.
Hub API End Points
Full PDF documentation gives the detail for all of the end points.
Creating Hash for Provisioning APIs
In order to receive an authorization token to access the provisioning APIs, a user much submit an encrypted value based on their secret key, user id and a user defined salt/nonce. The user then passes the id, nonce/salt, and hash as an object in the request body to the 'POST: /token' route to receive a token. All other provisioning APIs are not accessible without a token.
Note: values defined inside <> are meant to be filled in by the user with their credentials. For example, <user-id> should be replaced with the user's actual id.
{
"userId": <user-id>,
"nonce": <user-defined-string>,
"hash": <hashed-value>
}
How to create the hash by language
Cryptographic Hash: SHA-256
Return Value: Hexadecimal representation of binary hash
NOTE: DO NOT REUSE THE SAME NONCE/SALT. CHOOSE DIFFERENT VALUES EACH TIME YOU CALL THE '/TOKEN' ROUTE
NodeJS
var crypto = require('crypto');
var hashSecret = function(secret, nonce) {
var hash = crypto.createHash('sha256');
hash.update(secret + nonce);
return hash.digest('hex');
}
Ruby
require 'digest'
def hashSecret(secret, nonce)
Digest::SHA256.hexdigest(secret + nonce)
end
Python
import hashlib
def hashSecret(secret, nonce):
return hashlib.sha256(secret + nonce).hexdigest()
PHP
<?php
function hashSecret($secret, $nonce) {
return hash('sha256', $secret . $nonce);
}
?>
Javascript Code Sample
/*
* This code sample creates an new organization, user and project.
* First, you need to get an authorization token using your
* secret key from the UI (only admins can generate secret keys).
* You must include the token in the headers to
* access the user, org, and project routes if you have an authorization token.
* Information on creating the hash for the /token route can be found at <somewhere-wonderful>
*/
// library for making http calls
var request = require('request');
// library to generate unique time based nonce for hashing secret
var UUID = require('uuid-js');
// header object for requests
var headers = {
// authorization will be set after an authorization token is generated
'authorization': null,
'content-type': 'application/json'
}
// url for creating authorization token - replace <url> with your end point
var tokenPostURL = '<url>' + '/api/v1/token';
// url to post users
var userPostURL = '<url>' + '/api/v1/user/org/';
// url to post a new organization
var orgPostURL = '<url>' + '/api/v1/org';
// url to post a project
var projectPostURL = '<url>' + '/api/v1/project/org/';
// url to put template connections in a project
var projectPutURL = '<url>' + '/api/v1/project/';
// credentials that need to be passed in order to get an authorization token
// for more information on creating the hash see <other-wonderful-stuff>
var credentials = {
// user id must be passed in as a string
id: '1',
// time based uuid id as nonce
nonce: UUID.create(1).toString(),
hash: '<insert-hashed-value>'
}
// new organization information (to be used in the request body)
// We are not passing in a parentId and will instead use the default
var newOrganization = {
name: 'Brand Spanking New Org'
}
// user array to add to request body
var users = [
{
name: 'Steve Owner',
email: 'steve@owner.com',
role: 'Owner'
},
{
name: 'Sally Member',
email: 'sally@member.com',
role: 'Member'
},
{
name: 'Jack Admin',
email: 'jack@admin.com',
role: 'Member'
}
]
// new project information to be added to request body
var newProject = {
name: 'Best Project Ever',
description: 'This is the best project ever made :)',
// you can retrieve template ids from the GET '/org/:org_id/templates' route
templateId: '<insert-template-id>'
}
// construct the options passed as first parameter in all requests
var createOptions = function(url, body, id) {
return {
url: id ? url + id : url,
headers: headers,
body: JSON.stringify(body)
}
}
/*
* AUTHORIZATION STEP:
* Generate a token to access user, organization and project routes
*/
request.post(createOptions(tokenPostURL, credentials), function handleGenerateToken(err, res, body) {
if(err || (res && res.statusCode !== 200)) {
console.log(err || 'ERROR: ' + JSON.parse(body).metadata.message);
return;
}
// convert the response to json
var formattedTokenBody = JSON.parse(body).token;
// set the token in the headers object
headers.authorization = formattedTokenBody[0].token;
/*
* CREATE ORGANIZATION:
* Post a new organization to host the project and users
*/
request.post(createOptions(orgPostURL, newOrganization), function handleCreatedOrg(err, res, body) {
if(err || (res && res.statusCode !== 200)) {
console.log(err || 'ERROR: ' + JSON.parse(body).metadata.message);
return;
}
// extract the organization id from the response
var formattedOrgBody = JSON.parse(body).organizations;
var orgId = formattedOrgBody[0].id;
console.log('Congrats! You just created an organization!');
/*
* CREATE PROJECT:
* create a project and add it to the newly created organization
*/
request.post(createOptions(projectPostURL, newProject, orgId), function handleCreatedProject(err, res, body) {
if(err || (res && res.statusCode !== 200)) {
console.log(err || 'ERROR: ' + JSON.parse(body).metadata.message);
return;
}
console.log('Congrats! You just created a project!');
var formattedProjectBody = JSON.parse(body).projects;
// extract the new project id
var projectId = formattedProjectBody[0].id;
// check if response contains template connections
if(formattedProjectBody[0].templateConnections.length !== 0) {
// extract the template connections from the response body
// and format each connection into correct format for adding it to the project via a PUT
var templateConnections = formattedProjectBody[0].templateConnections.map(function(connection) {
return {
id: connection.id,
details: {
type: connection.type
}
}
});
/*
* ADD TEMPLATE CONNECTIONS:
* using the response from 'POST', you can now add template connections to the project
*/
request.put(createOptions(projectPutURL, { connections: templateConnections }, projectId), function handleTempConnections(err, res, body) {
if(err || (res && res.statusCode !== 200)) {
console.log(err || 'ERROR: ' + JSON.parse(body).metadata.message);
return;
}
console.log('Congrats! You just added template connections to your project!');
})
}
});
/*
* ADD TEMPLATE CONNECTIONS:
* add users to the newly created organization
*/
request.post(createOptions(userPostURL, users, orgId), function handleAddedUsers(err, res, body) {
if(err || (res && res.statusCode !== 200)) {
console.log(err || 'ERROR: ' + JSON.parse(body).metadata.message);
return;
}
var formattedUserBody = JSON.parse(body).users;
console.log('Congrats! You just added users to your organization!');
});
});
});
Python Code Sample
List the organisations:
''' Sample Python Code for Xponent Journey Hub API '''
# Python Sample Code to create orgs etc
import hashlib
import uuid
import json
import requests
# Your API secret here - from Admin -> users
mysecret = 'mylongsecret'
# Your user Id - as a string
myId='1234567'
nonce = str(uuid.uuid1())
def hashSecret(secret, nonce):
return hashlib.sha256(secret + nonce).hexdigest()
# header object for requests
headers = {
# authorization will be set after an authorization token is generated
'authorization': None,
'content-type': 'application/json'
}
credentials = {
# user id must be passed in as a string
'userId' : myId,
# time based uuid id as nonce
'nonce': nonce,
'hash' : hashSecret(mysecret,nonce)
}
# url for creating authorization token - replace <url> with your end point
url='http://localhost:3000/api/v1/'
# Request the token using the calculate credentials
r = requests.post(url+'token',data=credentials)
if r.status_code <> 200 :
raise Exception(str(r.text))
# Now we should have a token
token = dict(r.json())['tokens'][0]['token']
# Get the organizations
r = requests.get(url+'org',headers={'Authorization' : token })
if r.status_code <> 200 :
raise Exception(str(r.text))
orgs=dict(r.json())['organizations']
# Print out the Orgs
print "\nList of Organizations:\n"
print "%20s\t%20s\t%20s" % ("Organization Name","Id","ParentId")
for o in sorted(orgs, key=lambda k: k['id']) :
print "%(name)20s\t%(id)20s\t%(parentId)20s" % o
# Create a new Org
r = requests.post(url+'org',headers={'Authorization':token},
data={ "name" : "neworg-"+str(uuid.uuid1())[:5], "parentId" : 1})
if r.status_code <> 200 :
raise Exception(str(r.text))
newOrg = dict(r.json())['organizations'][0]
print "\nSuccessfully created new Org %(name)s (%(id)s)" % newOrg
# Change the name of the org
r = requests.put(url+'org/'+newOrg['id'],headers={'Authorization':token},
data={ "name" : "changed-"+str(uuid.uuid1())[:5]})
if r.status_code <> 200 :
raise Exception(str(r.text))
changeOrg = dict(r.json())['organizations'][0]
print "\nSuccessfully changed org name %s to %s (%s) \n" % (newOrg['name'], changeOrg['name'], changeOrg['id'])
# Add it to the list of orgs
orgs.append(changeOrg)
# Clean down - delete the org we created
r = requests.delete(url+'org/'+changeOrg['id'],headers={'Authorization':token})
if r.status_code <> 200 :
raise Exception(str(r.text))
print "Successfully deleted Org %(name)s (%(id)s)" % changeOrg
Create and delete an organization:
''' Sample Python Code for Xponent Journey Hub API - Create and delete organization '''
# Python Sample Code to create orgs etc
from os import environ
import hashlib
import uuid
import json
import requests
# Check that we have the correct environment variables first before we do anything else
if environ.get('KW_HUB') is None or environ.get('KW_USERID') is None or environ.get('KW_SECRET') is None:
print('The KW_HUB, KW_USERID and KW_SECRET environment variables need to be set for this script')
exit(1)
# Hub Fully qualified hostname
myHub = environ.get('KW_HUB')
# Your user Id - as a string
myId = environ.get('KW_USERID')
# Your API secret here - from Account Settings page for this user
mySecret = environ.get('KW_SECRET')
# A nonce used to hash the secret
nonce = str(uuid.uuid1())
def hashSecret(secret, nonce):
return hashlib.sha256((secret+nonce).encode('utf-8')).hexdigest()
# header object for requests
headers = {
# authorization will be set after an authorization token is generated
'authorization': None,
'content-type': 'application/json'
}
credentials = {
# user id must be passed in as a string
'userId': myId,
# time based uuid id as nonce
'nonce': nonce,
'hash': hashSecret(mySecret, nonce)
}
# url for creating authorization token - replace <url> with your end point
url = 'https://{}/api/v1/'.format(myHub)
# Request the token using the calcculate credentials
r = requests.post(
url+'token', headers={'Content-Type': 'application/json'}, json=credentials)
if r.status_code != 200:
raise Exception(str(r.text))
# Now we should have a token
token = dict(r.json())['tokens'][0]['token']
#print '\nSuccessfully got token: '+token+'\n'
# Create a new Org
r = requests.post(url+'org', headers={'Authorization': token},
json={"name": "neworg-"+str(uuid.uuid1())[:5], "parentId": '1'})
if r.status_code != 200:
raise Exception(str(r.text))
newOrg = dict(r.json())['organizations'][0]
print(f'Successfully created new Org {newOrg["name"]} {newOrg["id"]}')
# Change the name of the org
r = requests.put(url+'org/'+newOrg['id'], headers={'Authorization': token},
json={"name": "changed-"+str(uuid.uuid1())[:5]})
if r.status_code != 200:
raise Exception(str(r.text))
changeOrg = dict(r.json())['organizations'][0]
print(f'Successfully changed org name {newOrg["name"]} to {changeOrg["name"]} ({changeOrg["id"]})')
# Clean down - delete the org we created
r = requests.delete(
url+'org/'+changeOrg['id'], headers={'Authorization': token})
if r.status_code != 200:
raise Exception(str(r.text))
print(f'Successfully deleted Org {changeOrg["name"]} ({changeOrg["id"]})')