Documentation Index

Fetch the complete documentation index at: https://help.csgi.com/llms.txt

Use this file to discover all available pages before exploring further.

📢 Announcement:  Xponent Journey Hub, Bill Explainer, and Xponent Communication Control April 2026 (26.1) Release Notes are now available – Check out the latest updates published on April 2026.

Journey Hub API

Prev Next

The Journey Hub API allows OEM organizations to automate the management of Organizations, Users, and Projects. 

Hub API Authentication Process

  1. 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".
  2. On the same page, copy your user id.
  3. Create the hash (see instructions below)
  4. Create a 'POST' request to the /token route (see 'Request Object' format below)
  5. 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 

  • /token
  • /org
  • /project
  • /user

Full PDF documentation gives the details for all of the endpoints. 

Creating Hash for Provisioning APIs

In order to receive an authorization token to access the provisioning APIs, a user must 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.

Values defined inside <> are meant to be filled in by the user with their credentials. For example, <user-id> should replace 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

Do not reuse the same nonce/salt. Choose different values each time you call the '/token' route.

var crypto = require('crypto');
var hashSecret = function(secret, nonce) {
	var hash = crypto.createHash('sha256');
	hash.update(secret + nonce);
	return hash.digest('hex');
}
require 'digest'
def hashSecret(secret, nonce)
  Digest::SHA256.hexdigest(secret + nonce)
end
import hashlib
def hashSecret(secret, nonce):
    return hashlib.sha256(secret + nonce).hexdigest()
<?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 organizations:

''' 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"]})')