Overview
The CAS API and Expert integration automatically builds a table in Expert based on data from the CAS API. This prevents the need for manual updates.

Integration Steps
Review the integration steps below. More information for select steps is below.
Step #
|
Task
|
Owner
|
1
|
Identify which tables will have the integration, who the SME is for the content (e.g., account team), and when the integration is needed by (e.g., cycle rollover, etc.).
|
E&K team
|
2
|
Identify the data needed for the table, including:
- What data to include.
- How to present the data.
- Timeline for updating data (e.g., data refresh at Applicant Portal launch, ongoing feed, etc.).
|
E&K team
|
3
|
Create the table "stub" (i.e., table header and column names).
|
E&K team
|
4
|
Create the AWS S3 folder, CAS API subscription, retrieve the backlog (if any), and place it in the AWS S3 folder.
|
Implementation team
|
5
|
Configure the BuildTable() function for the table.
|
E&K team
|
6
|
Test the BuildTable() function (e.g., test against backlog, test automated delivery, etc.).
|
E&K team
|
7
|
Publish the new table.
|
E&K team
|
Step 3: Create the Table Stub
You can use the existing table header for the new table stub. Be sure to edit the table properties and select First row for headings.
Step 4: Configure the CAS API Subscription
The CAS API subscription must be set before the file can be sent to the AWS S3 bucket. On Amazon, a script that combines the individual JSON entries into a combined file when a new file appears in the S3 bucket.
All files are delivered to subdirectories under the “CAS” directory; the file naming conventions are:
- CAS/atcas_organizations_<instanceId>/<instanceId>_<organizationId>.json
- CAS/csdcas_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json
AWS S3 Bucket Details
Bucket: services-liaison
Region: us-east-1
Access key: AKIARGNW5KGAKUEUCHHH
Secret key: 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
CAS API Account
Username: HelpCenter
Password (Prelaunch and Production): p1NbG9B6n
API key: rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0
User level: Instance
CAS API Subscriptions
Endpoint: POST/v1/applicationForms/{applicationFormId}/subscriptions
Request Bodies:
Be sure to replace "CASNAMEHERE" with the name of the relevant CAS in lowercase letters (e.g. "atcas_...")
The program payloads feed pages called "Participating Programs", while the organization payloads mostly feed pages called "GRE Codes"
PROGRAMS
02 | "notificationEmailAddress" : "training@liaisonedu.com" , |
03 | "subscriptionDetails" : [ |
05 | "event" : "program.created" , |
06 | "responseType" : "fullResponse" , |
07 | "destinationType" : "AWSS3" , |
10 | "awsS3PathTemplate" : "CAS/CASNAMEHERE_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json" , |
11 | "awsS3BucketName" : "services-liaison" , |
12 | "awsS3Region" : "us-east-1" , |
13 | "awsS3AccessKey" : "AKIARGNW5KGAKUEUCHHH" , |
14 | "awsS3SecretKey" : "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" |
18 | "event" : "program.updated" , |
19 | "responseType" : "fullResponse" , |
20 | "destinationType" : "AWSS3" , |
23 | "awsS3PathTemplate" : "CAS/CASNAMEHERE_programs_<instanceId>/<instanceId>_<organizationId>_<programId>.json" , |
24 | "awsS3BucketName" : "services-liaison" , |
25 | "awsS3Region" : "us-east-1" , |
26 | "awsS3AccessKey" : "AKIARGNW5KGAKUEUCHHH" , |
27 | "awsS3SecretKey" : "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" |
ORGANIZATIONS
02 | "notificationEmailAddress" : "training@liaisonedu.com" , |
03 | "subscriptionDetails" : [ |
05 | "event" : "organization.created" , |
06 | "responseType" : "fullResponse" , |
07 | "destinationType" : "AWSS3" , |
10 | "awsS3PathTemplate" : "CAS/CASNAMEHERE_organizations_<instanceId>/<instanceId>_<organizationId>.json" , |
11 | "awsS3BucketName" : "services-liaison" , |
12 | "awsS3Region" : "us-east-1" , |
13 | "awsS3AccessKey" : "AKIARGNW5KGAKUEUCHHH" , |
14 | "awsS3SecretKey" : "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" |
18 | "event" : "organization.updated" , |
19 | "responseType" : "fullResponse" , |
20 | "destinationType" : "AWSS3" , |
23 | "awsS3PathTemplate" : "CAS/CASNAMEHERE_organizations_<instanceId>/<instanceId>_<organizationId>.json" , |
24 | "awsS3BucketName" : "services-liaison" , |
25 | "awsS3Region" : "us-east-1" , |
26 | "awsS3AccessKey" : "AKIARGNW5KGAKUEUCHHH" , |
27 | "awsS3SecretKey" : "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" |
Step 4 and a 1/2: Batch Retrieve Payloads (if necessary)
As of now, subscriptions to the organization.created and organization.updated events will not fire upon cycle rollover. Organizations, in fact, are not bound by cycles, but a table associates organizations with application forms (unicas_config.dbo.associationOrg). For CAS API-Help Center integrations that use organization payloads, it will be necessary to manually retrieve the organizations and upload them to the appropriate folder in the S3 bucket.
Steps
- Retrieve the relevant payloads in bulk using the appropriate script below
- Don't run any other API calls with the HelpCenter user account while the script is running!!!
- Drop the created folder into the S3 bucket (see above for credentials)
- Load the newly created folder into the /services-liaison/CAS/ folder on the S3 bucket
Tools
Python https://www.python.org/downloads/
PyCharm https://www.jetbrains.com/pycharm/do...ection=windows
CyberDuck (log into S3 bucket) https://cyberduck.io/download/
Scripts
Organizations
005 | from datetime import datetime |
009 | print ( "credentials, root url, application form id, organization id" ) |
011 | applicationFormId = 6801 |
012 | username = 'HelpCenter' |
013 | password = 'p1NbG9B6n' |
014 | apiKey = 'rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0' |
016 | saveDir = f 'C:\\Users\\gmartin\\OneDrive - Liaison International\\001_Projects\\Client_Projects\\MindTouch\\{casName}_organizations_{applicationFormId}\\' |
018 | print (applicationFormId) |
025 | runtime = datetime.now() |
026 | runtimestamp = runtime.strftime( "%Y-%m-%dT%H%M" ) |
030 | print ( "Define a function for authorization" ) |
031 | def auth(apiKey, UserName, Password, baseUrl): |
040 | endpoint = "/v1/auth/token" |
041 | url = baseUrl + endpoint |
042 | payload = "{\"UserName\": \"" + UserName + "\",\"Password\": \"" + Password + "\"}" |
044 | 'Content-Type' : "application/json" , |
047 | response = requests.request( "POST" , url, data = payload, headers = headers) |
048 | responseJson = response.json() |
049 | token = responseJson[ "Token" ] |
055 | print ( "Define a function for interacting with the CAS API" ) |
056 | def casapi(endpoint, apiKey, UserName, Password, baseUrl = "https://api.liaisonedu.com" , requestType = "GET" , payload = "", contentJson = False , extraHeaders = {}, urlParams = {}): |
071 | if len (urlParams) = = 0 : |
072 | url = baseUrl + endpoint |
076 | for urlParam, urlParamValue in urlParams.items(): |
078 | urlQueryPart = "?" + urlParam + "=" + str (urlParamValue) |
080 | urlQueryPart = "&" + urlParam + "=" + str (urlParamValue) |
081 | urlQueryStr + = urlQueryPart |
083 | url = baseUrl + endpoint + urlQueryStr |
084 | token = auth(apiKey = apiKey, UserName = UserName, Password = Password, baseUrl = baseUrl) |
087 | 'Authorization' : token, |
089 | 'Content-Type' : "application/json" |
091 | headers.update(extraHeaders) |
094 | 'Authorization' : token, |
097 | headers.update(extraHeaders) |
098 | response = requests.request(requestType, url, data = payload, headers = headers) |
102 | print ( '# make directory if not exist' ) |
103 | print ( 'directory already exists?\t\t' + str (os.path.isdir(saveDir))) |
104 | if not os.path.isdir(saveDir): |
106 | print ( 'directory created?\t\t' + str (os.path.isdir(saveDir))) |
109 | print ( '# retrieve organization list' ) |
110 | endpoint = f "/v1/applicationForms/{applicationFormId}/organizations" |
111 | organizationList = casapi(endpoint, apiKey, username, password, rootUrl, "GET" ).json() |
112 | print ( len (organizationList)) |
115 | print ( "# loop through organizations, retrieving details and saving as JSON" ) |
118 | nowtimestamp = now.strftime( "%Y-%m-%dT%H%M" ) |
120 | for organization in organizationList: |
122 | organizationId = organization[ "id" ] |
123 | endpoint = f "/v1/applicationForms/{applicationFormId}/organizations/{organizationId}" |
126 | nowtimestamp = now.strftime( "%Y-%m-%dT%H%M" ) |
127 | response = casapi(endpoint, apiKey, username, password, rootUrl, "GET" ) |
128 | organizationDetails = response.json() |
129 | print (nowtimestamp + "\t\t" + str (i) + "\t" + str ( len (organizationList) - i) + " left\t\t" + str (applicationFormId) + "\t" |
130 | + str (organizationId) + "\t\t" + str (response.status_code)) |
131 | if response.status_code = = 200 : |
132 | with open (saveDir + f "{applicationFormId}_{organizationId}.json" , 'w' ) as file : |
133 | json.dump(organizationDetails, file ) |
134 | print ( "\r\rretrieval complete" ) |
136 | runduration = (now - runtime).total_seconds() / 60 |
137 | print ( "run duration: " + str (runduration) + " minutes" ) |
Programs
005 | from datetime import datetime |
009 | print ( "credentials, root url, application form id, organization id" ) |
011 | applicationFormId = 6783 |
012 | username = 'HelpCenter' |
013 | password = 'p1NbG9B6n' |
014 | apiKey = 'rawsu40KkOUm77HDYOeCadparjOYtqddDaLW4og0' |
016 | saveDir = f 'C:\\Users\\gmartin\\OneDrive - Liaison International\\001_Projects\\Client_Projects\\MindTouch\\{casName}_programs_{applicationFormId}\\' |
018 | print (applicationFormId) |
025 | runtime = datetime.now() |
026 | runtimestamp = runtime.strftime( "%Y-%m-%dT%H%M" ) |
030 | print ( "Define a function for authorization" ) |
031 | def auth(apiKey, UserName, Password, baseUrl): |
040 | endpoint = "/v1/auth/token" |
041 | url = baseUrl + endpoint |
042 | payload = "{\"UserName\": \"" + UserName + "\",\"Password\": \"" + Password + "\"}" |
044 | 'Content-Type' : "application/json" , |
047 | response = requests.request( "POST" , url, data = payload, headers = headers) |
048 | responseJson = response.json() |
049 | token = responseJson[ "Token" ] |
055 | print ( "Define a function for interacting with the CAS API" ) |
056 | def casapi(endpoint, apiKey, UserName, Password, baseUrl = "https://api.liaisonedu.com" , requestType = "GET" , payload = "", contentJson = False , extraHeaders = {}, urlParams = {}): |
071 | if len (urlParams) = = 0 : |
072 | url = baseUrl + endpoint |
076 | for urlParam, urlParamValue in urlParams.items(): |
078 | urlQueryPart = "?" + urlParam + "=" + str (urlParamValue) |
080 | urlQueryPart = "&" + urlParam + "=" + str (urlParamValue) |
081 | urlQueryStr + = urlQueryPart |
083 | url = baseUrl + endpoint + urlQueryStr |
084 | token = auth(apiKey = apiKey, UserName = UserName, Password = Password, baseUrl = baseUrl) |
087 | 'Authorization' : token, |
089 | 'Content-Type' : "application/json" |
091 | headers.update(extraHeaders) |
094 | 'Authorization' : token, |
097 | headers.update(extraHeaders) |
098 | response = requests.request(requestType, url, data = payload, headers = headers) |
102 | print ( '# make directory if not exist' ) |
103 | print ( 'directory already exists?\t\t' + str (os.path.isdir(saveDir))) |
104 | if not os.path.isdir(saveDir): |
106 | print ( 'directory created?\t\t' + str (os.path.isdir(saveDir))) |
109 | print ( '# retrieve program list' ) |
110 | endpoint = f "/v1/applicationForms/{applicationFormId}/programs" |
111 | programList = casapi(endpoint, apiKey, username, password, rootUrl, "GET" ).json() |
112 | print ( len (programList)) |
115 | print ( "# loop through programs, retrieving details and saving as JSON" ) |
118 | nowtimestamp = now.strftime( "%Y-%m-%dT%H%M" ) |
120 | for program in programList: |
124 | organizationId = program[ "orgId" ] |
125 | programId = program[ "id" ] |
126 | endpoint = f "/v1/applicationForms/{applicationFormId}/organizations/{organizationId}/programs/{programId}" |
129 | nowtimestamp = now.strftime( "%Y-%m-%dT%H%M" ) |
130 | response = casapi(endpoint,apiKey,username,password,rootUrl, "GET" ) |
131 | programDetails = response.json() |
132 | print (nowtimestamp + "\t\t" + str (i) + "\t" + str ( len (programList) - i) + " left\t\t" + str (applicationFormId) + "\t" |
133 | + str (organizationId) + "\t" + str (programId) + "\t\t" + str (response.status_code)) |
134 | if response.status_code = = 200 : |
135 | with open (saveDir + f "{applicationFormId}_{organizationId}_{programId}.json" , 'w' ) as file : |
136 | json.dump(programDetails, file ) |
137 | print ( "\r\rretrieval complete" ) |
139 | runduration = (now - runtime).total_seconds() / 60 |
140 | print ( "run duration: " + str (runduration) + " minutes" ) |
Step 5: Configure the BuildTable Function
- Edit the page and insert the BuildTable script (https://help.liaisonedu.com/Template...tom/BuildTable), which downloads a combined JSON file and renders it based on the configuration options provided.
- On each table page, there are 3 components:
- Reference to the BuildTable script (usually collapsed).
- Configuration settings for the table (usually collapsed).
- Table with headers (these can be styled, resized, etc. as needed). The rows are filled in by the script.

Step B: Configure Table Settings
Sample options:
03 | var SELECTOR = 'table tbody' ; |
04 | var SORT_ORDER = [ "orgName" , "name" , "type" , "deadlineDisplay" ]; |
05 | var DISPLAY_ORDER = [ "state" , "orgName" , "name" , "type" , "deadlineDisplay" , |
06 | "programAttributes.supplementalApplicationRequired" , |
07 | "programAttributes.supplementalFeeRequired" , |
08 | "programAttributes.greRequired" ]; |
09 | var IGNORE = function (item) { |
10 | return item.status == "draft" || item.status == "review" ; |
13 | var STYLE = function (row) { |
16 | if (row.concentration == "Application submitted by the deadline" ) { |
17 | styles[ "deadlineDisplay" ] = "background: #3498db; font-weight: bold;" ; |
19 | if (row.concentration == "Application and transcripts received by deadline" ) { |
20 | styles[ "deadlineDisplay" ] = "background: #f39c12; font-weight: bold;" ; |
22 | if (row.concentration == "Application verified by deadline" ) { |
23 | styles[ "deadlineDisplay" ] = "background: #27ae60; font-weight: bold;" ; |
30 | buildTable(data_url, {SELECTOR: SELECTOR, SORT_ORDER: SORT_ORDER, DISPLAY_ORDER: DISPLAY_ORDER, IGNORE: IGNORE, STYLE: STYLE}); |
The options include:
- data_url: the data file to load, in the following format. Note that the ID is unique to a CAS and cycle and is stored in the Master Delivery Schedule.
- SELECTOR: CSS selector for the table to use. If there are multiple tables on the page, you can target a specific one. Otherwise, just leave the default.
- SORT_ORDER: an array of fields to sort by (such as "orgName" first, then "name," etc.).
- DISPLAY_ORDER: an array of fields to display in the table columns, from left to right. Field names may be standard attributes ("state") or custom attributes ("programAttributes.attributeName"). In custom attributes, the programAttributes[].displayName should be used for filtering (corresponding to the value entered by the user in the “AttributeName” parameter - var DISPLAY_ORDER = ["name", "etsCode", "programAttributes.AttributeName"]; ). The value to be displayed in the row in the table would be programAttributes.attributeValues.attributeValue.
- IGNORE: a function that determines which rows to ignore (e.g., ignore items with a status). The incoming item is an object based on the JSON entries.
- STYLE: a function that determines the inline CSS style to apply to each cell. The incoming item is an object based on the JSON entries. The return value should be an object with an inline CSS style for each field name. For example:
styles["fieldName"] = "background: #aa0000;"
. You may optionally set a value for styles.default
: this inline CSS will be added to cells where an exact field match isn't already there.
Step C: Set Up Table
Be sure that the table stub is already created with header rows labeled. Columns should match the DISPLAY_ORDER configuration settings, both in name and order.
Sample table:
State
|
Institution |
Program
|
Program Type
|
Program Deadline
|
Supplemental Application Required |
Supplemental Fee Required |
GRE Required |
Sample table HTML:
01 | < table border = "1" cellpadding = "1" cellspacing = "1" class = "mt-responsive-table mt-table-big" id = "program-table" > |
04 | < td class = "mt-noheading mt-column-width-10" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" > |
05 | < p >< span class = "mt-font-size-12" >< strong >< span class = "mt-color-ffffff" >State</ span ></ strong ></ span ></ p > |
07 | < td class = "mt-noheading mt-column-width-10" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" >< span class = "mt-font-size-12" >< font color = "#ffffff" >< b >Institution</ b ></ font ></ span ></ td > |
08 | < td class = "mt-noheading mt-column-width-15" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" > |
09 | < p >< span class = "mt-font-size-12" >< strong >< span class = "mt-color-ffffff" >Program</ span ></ strong ></ span ></ p > |
11 | < td class = "mt-noheading mt-column-width-15" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" > |
12 | < p >< span class = "mt-font-size-12" >< strong >< span class = "mt-color-ffffff" >Program Type</ span ></ strong ></ span ></ p > |
14 | < td class = "mt-noheading mt-column-width-10" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" > |
15 | < p >< span class = "mt-font-size-12" >< strong >< span class = "mt-color-ffffff" >Program Deadline</ span ></ strong ></ span ></ p > |
17 | < td class = "mt-noheading mt-column-width-10" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" >< span class = "mt-font-size-12" >< font color = "#ffffff" >< b >Supplemental Application Required</ b ></ font ></ span ></ td > |
18 | < td class = "mt-noheading mt-column-width-10" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" >< span class = "mt-font-size-12" >< strong >< span class = "mt-color-ffffff" >Supplemental Fee Required</ span ></ strong ></ span ></ td > |
19 | < td class = "mt-noheading mt-column-width-15" style = "background-color:rgb(30, 87, 160);vertical-align:middle;" >< span class = "mt-font-size-12" >< font color = "#ffffff" >< b >GRE Required</ b ></ font ></ span ></ td > |
Additional Style Code
If needed, you can use the codes below to further customize program data.
Make text invisible via CSS styling if there's a value you don't want users to see (NAFCAS example)
01 | var STYLE = function(row) { |
04 | if (typeof row.campus == 'undefined' ) { |
05 | styles[ "campus" ] = "opacity: 0" ; // hide the text - using opacity means users can't highlight the invisible text with the cursor |
Apply snippets of default answer values when the answer values themselves aren't being picked up (CASPA example)
01 | var STYLE = function(row) { |
04 | if (row.track.includes( "Blue:" )) { |
05 | styles[ "deadlineDisplay" ] = "background: #3498db; font-weight: bold;" ; // blue |
07 | if (row.track.includes( "Orange:" )) { |
08 | styles[ "deadlineDisplay" ] = "background: #f39c12; font-weight: bold;" ; // orange |
10 | if (row.track.includes( "Green:" )) { |
11 | styles[ "deadlineDisplay" ] = "background: #27ae60; font-weight: bold;" ; // green |
Fixed table headers
To add the fixed table headers functionality, you must add a specific class name to the <table> tag. Note that the fixed table header doesn't work in mobile. Instead, the default behavior was selected for ease of reading.
- Edit the page and click HTML in the top right of the editor.
- Enter the following tag in the <table> script:
- For a single-header table, add class="stick-one". If the table already has class attributes, then you can add stick-one to the existing class list. You must also edit the table properties and select First row for headings.
- For a double-header table, add class="stick-two". If the table already has class attributes, then you can add stick-two to the existing class list.
