1 - Introduction to SWS

Introduction to SWS project and concepts

1.1 - Introduction to SWS

A quick introduction to SWS and how to get started.

SWS is a PeopleSoft module that understands your metadata and turns PeopleSoft data into clean REST APIs, ready for AI, modern applications, and partner integrations. Deploy production web services in under 5 minutes through configuration alone.

Welcome to the SWS product created by Cedar Hills Group, Inc.

  • SWS is a PeopleSoft extension delivered as a standard Application Designer project that allows you to create web services with configuration only. We assume you read about it on the SWS Product Page.
  • Check out the Your First SWS Service to see how fast you can create web services with configuration only.
  • You should first read about SWS Concepts to get an understanding of how to think about SWS configuration and requests.
  • Then you need to get SWS installed using the installation documentation
  • Then read about the detailed SWS Configuration Options
  • Not sure why you should use web services? I would check out the Why Use Web Services? chapter of our book.

When you install the SWS from Cedar Hills Group, it has two PeopleSoft web services that have very different use cases and audiences.

  • SWS Get Services - You will use this method 95% of the time.
  • PsoftQL Web Services
    • This is a more advanced method to extract PeopleSoft data for a system integrator or outside vendor.
    • You generally will NOT open up this web service to many users. This is geared toward internal integration tools like Mulesoft, Snaplogic, and Boomi.

Not sure which one fits your situation? See Which SWS Method Should I Use? for a visual decision flowchart covering both services and the SQL-vs-PsoftQL sub-choice inside a config.

SWS Get Services

The easiest and most secure way to expose your PeopleSoft data is by using SWS-configured web services. This method gives the PeopleSoft admin control over what data is exposed to third parties and is a more secure model for most use cases. You can read about how to set up a new web service in the SWS Configuration Section.

  • A PeopleSoft power user
    • Configures a new SWS configuration with either SQL or PsoftQL Syntax.
    • Defines parameters and filters
    • Grants security
    • Give URL and Authentication information to the integration partner.

The integration partner ends up using a REST GET Service Operation called CHG_SWS. This web service checks security, resolves parameters, executes the SQL, encodes the data and returns it to the user.

  • SWS Get Services - Standard HTTP Clients - Service Operation: CHG_SWS
    • Most of the web services you configure with SWS will be set up to be used by non-trusted users.
    • This is the default way to configure SWS services.
    • You can start reading about how to set up a new web service in the SWS Configuration Section

(Advanced) PsoftQL Web Services

This is a more advanced method for a system integrator. You generally will NOT open up this web service to many users.

  • PsoftQL Web Services - Service Operation: CHG_SWS_PSOFTQL
    • This web service allows a highly trusted integration platform to request data in PsoftQL Syntax
    • This is for internal integration tools like Mulesoft, Snaplogic and Boomi.
    • When developing and prototyping new SWS configurations, your PeopleSoft admins may use this method to execute PsoftQL against PeopleSoft before configuring it in the most secure and locked-down SWS Get Service.

1.2 - Your First SWS Service

Let’s create your first SWS service to see how easy it is to extract information from PeopleSoft.

SWS combines two products. One product is advanced and caters to a specific group of users. The other has a broader application. In our first example, we’ll concentrate on the simplest method to create a web service. This involves crafting a SQL statement that can be exposed as a web service. This documentation will focus on the other areas of SWS. In this first example, we will focus on a quick win to expose a list of users and their emails.

Define Our Use Case

  • In this use case, the objective is to provide a list of users with their names, employee IDs, and primary email addresses to an external system.
  • Let’s further assume that the external system is going to have several other data requests that will be custom to this system/vendor. Therefore, we will “scope” our APIs to the vendor system in the URL path which will make more sense as we progress through this example. We will use “acmecorp” as the vendor name in our URL path.
  • We also know from the vendor that they need specific field names in the output as they cannot handle data translation. Therefore, we will need to rename the fields in our output.

Create a SQL Statement

The first step is to create a SQL statement that will return the data we need. In this case, we will use the following SQL statement. This was generated by PS Query and copied out but you can do what magic SQL you want.

SELECT B.EMPLID, B.NAME, A.EMAIL_ADDR
Β  FROM PS_EMAIL_ADDRESSES A, PS_PERSON_NAME B
Β  WHERE  A.PREF_EMAIL_FLAG = 'Y'
Β Β Β Β  AND A.EMPLID = B.EMPLID 

Create the SWS Service

  • Navigate to: CHG Custom -> Simple Web Service Setup
  • Use “Add Mode” to add a new value.
  • Unique Identifier: This is auto-generated by SAVE.
  • Description & Notes: Any Metadata you want to add to the service.
  • URL Path: This is the URL path that will be used to access the service. In this case, we will use acmecorp as the vendor name in our URL path. We further added /people/email to have a full path of /acmecorp/people/email. This will be in the URL that the vendor will use to access the service.
  • Request Type: SQL
  • SQL Statement: The SQL statement we created above.
  • Output Fields: This is where we will rename the fields to match the vendor’s requirements. We will rename the fields to UserID, LegalName, and PrimaryEmail. These fields were communicated by the vendor.
  • The second half of the page contains some miscellaneous options that we will not cover in this example. We will cover these in other examples. Leave the defaults.

  • Parameters: The parameters section does not need any values as the vendor will NOT pass in any parameters to filter or limit the data for this simple example.

  • Allowed Permission Lists: This is important and defines how users can access the system. We cover this in detail in the Security section. We granted the web service to our admin permission list and a new permission list we created just for ACME. We created a user to represent the ACME Corp server and the security structure looks like this:

    • OPRID: ACMECORP_SWS_USER
      • Password: phoebe-PROBABLE-dallas
      • Role: ACMECORP_SWS_USER
        • Permission List: ACMECORP_SWS_USER
          • Web Service: CHG_SWS.CHG_SWG_GET

We can go to the last table and get the URL to test the service. We will use the URL in the next section. We give you HTTP and curl syntax.

I like HTTP Syntax as I think it is very clear. Let’s create our base64 encoded user name and password (Postman can do this for you) and ask for JSON accept: application/json. We will see if a response comes back. I am removing some of the data because we did not have a row limit specified and we have not implemented pagination which is possible but those are more advanced topics to cover later.


GET http://psft-cs-858.c.peoplesoftdemo-1470578694381.internal:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/acmecorp/people/email
accept: application/json
authorization: Basic QUNNRUNPUlBfU1dTX1VTRVI6cGhvZWJlLVBST0JBQkxFLWRhbGxhcw==


HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-type: application/json; encoding=UTF-8
date: Thu, 21 Sep 2023 01:03:27 GMT
transfer-encoding: chunked
x-oracle-dms-ecid: 8458013e-3913-40bb-9698-190771ec7d9a-0000001f
x-oracle-dms-rid: 0
x-peoplesoftrequestid: ad31d07e-581a-11ee-a26c-7f8dcd811d6d
x-success: True

{
  "data": [
    {
      "UserID": "FAI036",
      "LegalName": "Shard Yahq",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FAEQT0001",
      "LegalName": "Sandra Falix",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FAEQT0003",
      "LegalName": "Ortega Pariara",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FAEQT0008",
      "LegalName": "Rinda Marnar",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FAEQT0012",
      "LegalName": "Judith Alkins",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FASS0150",
      "LegalName": "Timothy Rond",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FASS0153",
      "LegalName": "Tim Rihela",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FASS0154",
      "LegalName": "Tona Ricado",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    },
    {
      "UserID": "FASSFAN001",
      "LegalName": "Apple Fan",
      "PrimaryEmail": "HCMGENUser1@ap6023fems.us.oracle.com"
    }
  ],
  "errors": "",
  "meta": {
    "rowCount": "4011",
    "sqlIDExecuted": "d7fb7404-b1dc-4acd-b062-6d9f529f9b00",
    "success": "True",
    "debugMessages": "",
    "QueryString": "NULL",
    "URLPath": "acmecorp/people/email",
    "finalSQL": "SELECT B.EMPLID, B.NAME, A.EMAIL_ADDR\nΒ  FROM PS_EMAIL_ADDRESSES A, PS_PERSON_NAME B\nΒ  WHERE  A.PREF_EMAIL_FLAG = 'Y'\nΒ Β Β Β  AND A.EMPLID = B.EMPLID",
    "productVersion": "2023-06-07",
    "toolsVer": "8.58.07",
    "currentUser": "ACMECORP_SWS_USER",
    "responseDTTM": "2023-09-21 01:03:29.000000",
    "psftTransactionId": "ad31d07e-581a-11ee-a26c-7f8dcd811d6d",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "ServerDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessage": ""
  }
}

If you go back and look at the setup in the miscellaneous section we asked for metadata to be generated and we get back this JSON object which provides very helpful information for debugging.

"meta": {
    "rowCount": "4011",
    "sqlIDExecuted": "d7fb7404-b1dc-4acd-b062-6d9f529f9b00",
    "success": "True",
    "debugMessages": "",
    "QueryString": "NULL",
    "URLPath": "acmecorp/people/email",
    "finalSQL": "SELECT B.EMPLID, B.NAME, A.EMAIL_ADDR\nΒ  FROM PS_EMAIL_ADDRESSES A, PS_PERSON_NAME B\nΒ  WHERE  A.PREF_EMAIL_FLAG = 'Y'\nΒ Β Β Β  AND A.EMPLID = B.EMPLID",
    "productVersion": "2023-06-07",
    "toolsVer": "8.58.07",
    "currentUser": "ACMECORP_SWS_USER",
    "responseDTTM": "2023-09-21 01:03:29.000000",
    "psftTransactionId": "ad31d07e-581a-11ee-a26c-7f8dcd811d6d",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "ServerDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessage": ""
  }

Do you want XML instead? Simply change the accept header.


GET http://psft-cs-858.c.peoplesoftdemo-1470578694381.internal:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/acmecorp/people/email
accept-encoding: gzip, deflate, br
accept: application/xml
authorization: Basic QUNNRUNPUlBfU1dTX1VTRVI6cGhvZWJlLVBST0JBQkxFLWRhbGxhcw==
user-agent: httpyac

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-type: application/xml; encoding=UTF-8
date: Thu, 21 Sep 2023 01:16:11 GMT
transfer-encoding: chunked
x-oracle-dms-ecid: 8458013e-3913-40bb-9698-190771ec7d9a-00000020
x-oracle-dms-rid: 0
x-peoplesoftrequestid: 7343166f-581c-11ee-a26c-7f8dcd811d6d
x-success: True
<?xml version="1.0"?>
<response>
  <data>
    <row>
      <UserID><![CDATA[FAI036]]></UserID>
      <LegalName><![CDATA[Shard Yahq]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>
    <row>
      <UserID><![CDATA[FAEQT0001]]></UserID>
      <LegalName><![CDATA[Sandra Falix]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>
    <row>
      <UserID><![CDATA[FAEQT0003]]></UserID>
      <LegalName><![CDATA[Ortega Pariara]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>
    <row>
      <UserID><![CDATA[FAEQT0008]]></UserID>
      <LegalName><![CDATA[Rinda Marnar]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>
    <row>
      <UserID><![CDATA[FAEQT0012]]></UserID>
      <LegalName><![CDATA[Judith Alkins]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>
    <row>
      <UserID><![CDATA[FASS0150]]></UserID>
      <LegalName><![CDATA[Timothy Rond]]></LegalName>
      <PrimaryEmail><![CDATA[HCMGENUser1@ap6023fems.us.oracle.com]]></PrimaryEmail>
    </row>

  </data>
  <errors></errors>
  <meta>
    <rowCount>4011</rowCount>
    <sqlIDExecuted>d7fb7404-b1dc-4acd-b062-6d9f529f9b00</sqlIDExecuted>
    <peopleSoftRequestID>7343166f-581c-11ee-a26c-7f8dcd811d6d</peopleSoftRequestID>
    <success>True</success>
    <dbName>CS92U020</dbName>
    <QueryString><![CDATA[NULL]]></QueryString>
    <URLPath><![CDATA[acmecorp/people/email]]></URLPath>
    <finalSQL><![CDATA[SELECT B.EMPLID, B.NAME, A.EMAIL_ADDR
Β  FROM PS_EMAIL_ADDRESSES A, PS_PERSON_NAME B
Β  WHERE  A.PREF_EMAIL_FLAG = 'Y'
Β Β Β Β  AND A.EMPLID = B.EMPLID]]></finalSQL>
    <productVersion>2023-06-07</productVersion>
    <peopleSoftUser>ACMECORP_SWS_USER</peopleSoftUser>
    <peopleToolsVersion>8.58.07</peopleToolsVersion>
    <requestTime>2023-09-21-01.16.11.000000</requestTime>
    <debugMessage><![CDATA[]]></debugMessage>
  </meta>
</response>

Or how about CSV? Just change the accept header. You will notice with CSV any metadata is in the header and the data is in the body. This is a common pattern with CSV.

GET http://psft-cs-858.c.peoplesoftdemo-1470578694381.internal:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/acmecorp/people/email
accept-encoding: gzip, deflate, br
accept: text/csv
authorization: Basic QUNNRUNPUlBfU1dTX1VTRVI6cGhvZWJlLVBST0JBQkxFLWRhbGxhcw==
user-agent: httpyac

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-type: text/csv; encoding=UTF-8
date: Thu, 21 Sep 2023 01:18:36 GMT
transfer-encoding: chunked
x-dbname: CS92U020
x-errors: 
x-peoplesoftrequestid: ca315226-581c-11ee-a26c-7f8dcd811d6d
x-peoplesoftuser: ACMECORP_SWS_USER
x-peopletoolsversion: 8.58.07
x-productversion: 2023-06-07
x-requesttime: 2023-09-21-01.18.37.000000
x-rowcount: 4011
x-sqlidexecuted: d7fb7404-b1dc-4acd-b062-6d9f529f9b00
x-success: True
x-urlpath: acmecorp/people/email

"UserID","LegalName","PrimaryEmail"
"FAI036","Shard Yahq","HCMGENUser1@ap6023fems.us.oracle.com"
"FAEQT0001","Sandra Falix","HCMGENUser1@ap6023fems.us.oracle.com"
"FAEQT0003","Ortega Pariara","HCMGENUser1@ap6023fems.us.oracle.com"
"FAEQT0008","Rinda Marnar","HCMGENUser1@ap6023fems.us.oracle.com"
"FAEQT0012","Judith Alkins","HCMGENUser1@ap6023fems.us.oracle.com"
"FASS0150","Timothy Rond","HCMGENUser1@ap6023fems.us.oracle.com"
"FASS0153","Tim Rihela","HCMGENUser1@ap6023fems.us.oracle.com"
"FASS0154","Tona Ricado","HCMGENUser1@ap6023fems.us.oracle.com"
"FASSFAN001","Apple Fan","HCMGENUser1@ap6023fems.us.oracle.com"
"FASSFAN002","Honey Fan","HCMGENUser1@ap6023fems.us.oracle.com"
"FASSFAN003","Cookie Fan","HCMGENUser1@ap6023fems.us.oracle.com"
"FASSFAN004","Gavin Kravitz","HCMGENUser1@ap6023fems.us.oracle.com"
"FASSFAN005","KATHERINE HURADO","HCMGENUser1@ap6023fems.us.oracle.com

Summary

In this example, we created a simple web service that returned a list of users and their email addresses. We used a SQL statement to return the data and we renamed the fields to match the vendor’s requirements. We also showed how to test the service using HTTP and curl. We also showed how to get metadata back from the service to help with debugging. We also showed how to get the service to return XML and CSV.

This is a production-ready web service that was created in under 5 minutes. (Not counting the security setup.)

1.3 - Which SWS Method Should I Use?

A visual decision guide for choosing between CHG_SWS (config-based) and CHG_SWS_PSOFTQL, and for choosing SQL vs PsoftQL inside a config.

SWS ships with two PeopleSoft service operations and one important sub-choice inside the config-based service. This page is a visual, one-page answer to “which one do I use?” β€” pick the path at the bottom of the flowchart, then follow the link to the detail page for that path.

Most admins will land on CHG_SWS (config-based) with PsoftQL syntax β€” that is the 95% answer. The diagram below confirms when to reach for the other two options instead.

Decision Flowchart

flowchart TD
    start([Need to expose PeopleSoft data via REST]) --> q1{Is the caller a highly<br/>trusted internal<br/>integration platform?<br/><br/>e.g. MuleSoft,<br/>Snaplogic, Boomi}
    q1 -->|Yes, and they can craft PsoftQL themselves| psoftqlws[CHG_SWS_PSOFTQL<br/><br/>client sends PsoftQL<br/>in the POST body<br/><br/>advanced Β· high-trust only]:::advanced
    q1 -->|No β€” third-party,<br/>standard REST client,<br/>or locked-down use case| config[CHG_SWS<br/><br/>admin-configured GET service<br/>the 95% path]:::standard
    config --> q2{What syntax inside<br/>the config?}
    q2 -->|Need CSV output, or<br/>SQL-function data translation,<br/>or flat rowset data| sql[SQL statement]:::sql
    q2 -->|Need nested parent/child data,<br/>pagination, auto EFFDT /<br/>EFF_STATUS / EFFSEQ,<br/>or all fields auto-exported| psoftql[PsoftQL statement<br/><br/>the default inside most configs]:::psoftql

    classDef advanced fill:#fde2e2,stroke:#c0392b,stroke-width:2px,color:#222
    classDef standard fill:#dff0d8,stroke:#3c763d,stroke-width:2px,color:#222
    classDef sql fill:#e7f3fb,stroke:#289dd0,stroke-width:2px,color:#222
    classDef psoftql fill:#d9f3f3,stroke:#5ac5c5,stroke-width:2px,color:#222

Path 1 β€” CHG_SWS_PSOFTQL (Advanced)

This is the advanced service. The client POSTs a PsoftQL request body directly to PeopleSoft, and SWS returns structured JSON or XML that mirrors the PeopleSoft record layout. It is powerful β€” the caller can request any whitelisted table at will β€” but that power lives with the client, not with your admin configuration.

Only use CHG_SWS_PSOFTQL when the caller is a highly trusted internal integration platform such as MuleSoft, Snaplogic, or Boomi, and your team is comfortable that the caller can craft PsoftQL correctly. It is also useful while prototyping new SWS configurations. See Service Operation CHG_SWS_PSOFTQL for request/response details and the security model.

Path 2 β€” CHG_SWS (Config-Based GET)

This is the default path and what 95% of your web services will use. A PeopleSoft power-user configures the URL path, parameters, security grid, and the statement that runs β€” the client just calls a normal REST GET. All the PeopleSoft knowledge lives in the configuration row, not on the client.

Use CHG_SWS any time the caller is a third party, a standard REST client, or any integration where you want the admin (not the client) to define what data is exposed and how. See the SWS Configuration page for the full configuration walkthrough.

Sub-Decision β€” SQL or PsoftQL Inside the Config?

Inside a CHG_SWS configuration you choose one syntax type. Here is the short version of the trade-off:

Choose SQL when you need:

  • CSV output (PsoftQL does not produce CSV)
  • Data translation via SQL functions, joins across records, or aliases
  • Flat, row-shaped results rather than nested parent/child trees
  • Full hand-control over the WHERE clause, joins, and effective-date logic

Choose PsoftQL when you want:

  • Nested parent/child data (one person, many phones, many addresses) in a single response
  • Pagination out of the box
  • Automatic EFFDT, EFF_STATUS, and EFFSEQ handling β€” no SQL required
  • All record fields auto-exported without hard-coding the select list

For the full, authoritative feature comparison (JSON/XML/CSV support, pagination, effective-date handling, etc.) see the Syntax Types table in the SWS Configuration page.

At a Glance

1.4 - What is HTTP and REST

Understanding HTTP, REST APIs and Parameter Passing with HTTP

HTTP Quick Start

This is a quick and succinct background on HTTP and REST concepts. We provide this here to give readers who are new to the Web Service concept a short introduction to HTTP syntax and REST concepts, as this documentation assumes that you have a basic understanding of the terminology and concept. If you want to understand HTTP, see the recommended tools section for some recommendations.

In the world of web development, the Hypertext Transfer Protocol (HTTP) serves as the foundation for communication between web browsers and servers. HTTP enables the exchange of information, such as web pages, images, and data. In this article, we will explore the basic concepts of HTTP, including requests, responses, and the syntax used to send and receive information between clients and servers.

HTTP Requests

When you type a website URL into your web browser’s address bar and hit enter, your browser sends an HTTP request to the server hosting that website. HTTP requests are made up of several components:

  1. Request Method: The request method specifies the type of action the client wants to perform on the server. The two most common methods are:

    • GET: This method is used to retrieve data from the server. It is typically used when you visit a website or fetch information.
    • POST: This method is used to send data to the server. It is commonly used when you submit a form or upload a file.
  2. URL/URI: The Uniform Resource Locator (URL) or Uniform Resource Identifier (URI) identifies the specific resource the client wants to access. It typically includes the domain name and the path to the resource on the server.

  3. Headers: Headers contain additional information about the request, such as the type of content being sent, accepted content types, authentication credentials, and more. Headers provide crucial context to the server and allow the client and server to understand each other’s capabilities and requirements.

  4. Parameters: Parameters are additional pieces of data that can be included with an HTTP request. They provide specific instructions to the server or help filter and narrow down the requested data. Parameters are often used in GET requests as query parameters, allowing the client to pass data in the URL.

HTTP Responses

When the server receives an HTTP request, it processes the request and sends back an HTTP response. An HTTP response consists of the following components:

  1. Status Code: The status code indicates the outcome of the server’s attempt to fulfill the request. Some common status codes include:

    • 200 OK: The request was successful, and the server is returning the requested data.
    • 404 Not Found: The requested resource could not be found on the server.
    • 500 Internal Server Error: An unexpected error occurred on the server.
  2. Headers: Similar to request headers, response headers provide additional information about the response, such as the content type, cache-control directives, cookies, and more.

  3. Response Body: The response body contains the actual data or content requested by the client. For example, if the client requested a web page, the response body would contain the HTML content of that page.

Syntax and Examples

HTTP requests and responses follow a specific syntax. Here are examples of GET and POST requests:

  • GET Request:
GET /api/products?id=123 HTTP/1.1
Host: api.example.com
Accept: application/json

In the above example, the client is making a GET request to the server’s /api/products endpoint, passing the id parameter with a value of 123. The request also includes the Host header, specifying the server’s domain, and the Accept header, indicating that the client prefers a response in JSON format.

  • POST Request:
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "name": "John Doe",
  "email": "john@example.com"
}

In this example, the client is making a POST request to the server’s `/api

Understanding REST APIs and Parameter Passing with HTTP

Within the realm of HTTP, a powerful architectural style called REST (Representational State Transfer) has emerged as a standard for designing web APIs. SWS uses REST concepts to provide GET web services. Let’s quickly introduce a few concepts.

What is REST?

REST is an architectural style that provides a set of guidelines for designing networked applications. It emphasizes simplicity, scalability, and interoperability between systems. RESTful APIs (Application Programming Interfaces) enable different software applications to communicate with each other by leveraging the existing HTTP protocol.

In a RESTful architecture, resources (such as data or services) are identified by unique URLs called URIs (Uniform Resource Identifiers). These resources can be accessed and manipulated using standard HTTP methods like GET, POST, PUT, DELETE, etc. By following the principles of REST, developers can create APIs that are easy to understand, consume, and extend.

Understanding Parameters

In the context of REST APIs, parameters play a crucial role in specifying additional information for requests and responses. They allow clients (the applications making the requests) and servers (the applications serving the requests) to exchange data and perform specific actions.

Parameters can be classified into two main types: query parameters and path parameters.

  1. Query Parameters

Query parameters are used to filter, sort, or paginate data. They are appended to the end of a URL after a question mark ? and separated by ampersands &. For example:

GET https://api.example.com/products?category=electronics&sort=price&limit=10

In the above example, the query parameters are category=electronics, sort=price, and limit=10. These parameters provide additional instructions to the server, such as retrieving only electronic products, sorting them by price, and limiting the response to 10 items.

  1. Path Parameters

Path parameters are used to identify a specific resource within a URL path. They are denoted by a placeholder surrounded by curly braces ‘{}’. For example:

GET https://api.example.com/products/{id}

In the above example, the {id} placeholder represents a path parameter that would be replaced with an actual value when making a request. For instance, to retrieve the details of a product with an ID of 123, the URL would be:

GET https://api.example.com/products/123

Path parameters are useful when you want to interact with a specific resource or perform operations on it, such as updating or deleting it.

Conclusion

REST APIs have become the de facto standard for building web services due to their simplicity, scalability, and compatibility with the HTTP protocol. Understanding how parameters are passed using HTTP is essential for effectively working with RESTful APIs. By utilizing query parameters and path parameters, clients can provide additional instructions to the server and interact with specific resources. This empowers developers to build flexible and powerful applications that can seamlessly communicate with other services over the web.

1.5 - Recommended Tools

Recommend software to use for working with SWS.

When working with SWS to configure new web services you will want some simple tools on your machine that you can quickly test new services. Having your own local tools will speed up your development and troubleshooting time significantly.

The tools we recommend to use are:

  • Visual Studio Code - The only text editor you should be using!
    • A great text editor with a huge list of plugins for editing and working with XML, JSON, encodings, etc.
    • I really like the HTTPYac extension. It can replace Postman in many situations. It has some pretty good documentation.
      • The key benefit here is that your HTTP request syntax is in local text files and you can easily check them into Git or do other types of backups.
      • Chris uses this combination of tools to do 98% of development, troubleshooting and testing and is likely all you need.
  • Use Hurl.dev for Automated API testing.
    • You need to understand HTTP to use this tool and the learning curve can be steep. However, if you want to set up a simple automated HTTP test, I highly recommend learning it.
    • Hurl is used in the development of SWS!
  • Postman
    • This is the default choice for most people. However, I have found that it has gotten too bloated with features for what I use it for. However, it is a great product!
  • hoppscotch.io is a good alternative to Postman.
  • curl is also a tried and true tool for the techies.

Understanding HTTP Syntax

If you don’t have a firm understanding of HTTP concepts, I highly recommend that you read this concise book.

We will be documenting most HTTP calls in HTTP syntax in this documentation so you must have a general understanding of HTTP. If you prefer a more visual instruction I suggest you Search YouTube for HTTP introductions

1.6 - SWS Alternatives

This page contains some alternatives to SWS.

You might be reading this page because you are trying to decide if SWS is right for your organization. Let’s look at what you would do if you did not have SWS. There are a few alternatives to our SWS solution. Let’s see how they compare.

Bespoke Web Services

The main alternative to SWS is to create a bespoke web service for each client or vendor and each piece of data. This is the traditional way of doing things. It is expensive and time-consuming. It is also very inflexible.

If we take the PeopleSoft Campus module as an example, a software vendor may need to integrate with Student Enrollment, Configuration data like Terms and other Academic Setup data, Student Bio data, Student Grades, and Invoices. This is a very common integration pattern.

With each new vendor, a custom bespoke web service may need to be created. You often may have very similar web services returning mostly the same data but with some slight variations.

SWS can run in any PeopleSoft database, but our example here is focused on Campus Solutions.


left to right direction
skinparam sequenceArrowThickness 3
Title: The SWS Alternative! Web Service Sprawl!

Package "PeopleSoft" {


  rectangle "Terms" as rec.terms #FFD3B0
  rectangle "Student Bio data" as rec.students #FFD3B0
  rectangle "Student Grades" as rec.grades #FFD3B0
  rectangle "Student Enrollment" as rec.se #FFD3B0
  rectangle "Invoices" as rec.invoices #FFD3B0

  ' package "Bespoke Web Services" {
  rectangle "Terms\nWeb Service" as ib.rec.terms #FF6969
  rectangle "Student Bio data\nWeb Service" as ib.rec.students #FF6969 
  rectangle "Student Bio data\nWeb Service\nVersion 2" as ib.rec.students2 #FF6969 
  rectangle "Student Grades\nWeb Service" as ib.rec.grades #FF6969
  rectangle "Student Enrollment\nWeb Service" as ib.rec.se #FF6969
  rectangle "Student Enrollment\nWeb Service \nVersion 2" as ib.rec.se2 #FF6965
  rectangle "Invoices\nWeb Service" as ib.rec.invoices #FF6969
' }

  ib.rec.terms <-- rec.terms
  ib.rec.students <-- rec.students
  ib.rec.students2 <-- rec.students
  ib.rec.grades <-- rec.grades
  ib.rec.se <-- rec.se
  ib.rec.se2 <-- rec.se

  ib.rec.invoices <-- rec.invoices

  ' rectangle "SWS - Web Service\n\n(Single Web Service)\nReplaces all " as ib.sws #limeGreen
  ' ib.sws -[#limeGreen,thickness=3,dashed]-> rec.terms
  ' ib.sws -[#limeGreen,thickness=3,dashed]-> rec.students
  ' ib.sws -[#limeGreen,thickness=3,dashed]-> rec.grades
  ' ib.sws -[#limeGreen,thickness=3,dashed]-> rec.se
  ' ib.sws -[#limeGreen,thickness=3,dashed]-> rec.invoices


}


rectangle "LMS Integration Partner" as client #A6D0DD
rectangle "Student Success\nIntegration Partner" as client2 #A6D0DD
rectangle "Payment\nIntegration Partner" as client3 #A6D0DD

client <-- ib.rec.terms: GET
client <-- ib.rec.students: GET
client <-- ib.rec.grades: GET
client <-- ib.rec.se: GET


client2 <-- ib.rec.students2: GET
client2 <-- ib.rec.grades: GET
client2 <-- ib.rec.se2: GET

client3 <-- ib.rec.invoices: GET

' client -[#limeGreen,thickness=3,dashed]-> ib.sws
' client2 -[#limeGreen,thickness=3,dashed]-> ib.sws
' client3 -[#limeGreen,thickness=3,dashed]-> ib.sws

If you are a Software vendor trying to integrate with a PeopleSoft customer then you might develop 5 unique web services for the five unique areas you are trying to integrate with if you are NOT using SWS.

  • You have a PeopleSoft application with 5 different web services.
  • Each web service is designed for a specific vendor or client.
  • Each client may have customizations that are hard to support in the traditional approach without access to the client’s PeopleSoft development instances or support from the client’s PeopleSoft development team.
  • Each client may have different data filtering requirements.
  • Code patches can take months to deploy to all clients and some clients may not be able to take the patch at all or they don’t have the resources to deploy the patch.

We have developed vendor solutions similarly and this is the reason we developed SWS. We wanted to make it easier for vendors to integrate with PeopleSoft, and we wanted to make it easier for PeopleSoft customers to integrate with third-party vendors. You can read more in the SWS origin story

What is the cost of this approach?

You can ask a developer to create a new web service for each user integration requirement. If a vendor commissioned the PeopleSoft application team to develop a new web service, the following process would likely be followed. This assumes you are a medium to large organization with a formal development process.

After years of developing web services for PeopleSoft customers, we have a good idea of the cost of developing a new web service and even wrote a book about the topic.

TaskMinimum Estimated Person HoursMaximum Estimated Person Hours
Gather Requirements510
Write a technical specification510
Technical Design Review210
Create and unit test code840
Functional Testing840
Code Review210
Code Migration24
Bug Fixes410
Totals36 Hours - $3,600 @ $100/Hour130 Hours - $13,000 @ $100/Hour
  • If you value your developer’s time at roughly $100 an hour. Then the cost to develop a single new web service is somewhere between $3,600 and $13,000.
  • With SWS, that amount can be drastically reduced. In some cases, it can take just a few minutes to create a new web service and have it deployed in production.

Query Access Web Services

We have a KB article called Reporting Web Services: Using the REST Web Services to run a Query which demonstrates how you can make a “web service” out of a PeopleSoft query manager query. This can be an effective tool to generate web services.

However, the SWS offers many advantages over this delivered functionality.

  • You have better control over the field names in the output.
  • SWS offers more output encoding types
  • The PeopleSoft query tool imposes unneeded complexity with data security and query tree security that often just gets in the way.
  • SWS supports more advanced SQL and you are not forced into the SQL generated by query manager.

How does SWS Make it better?

  • SWS is a single web service that can be used for most integrations.
  • SWS is a single web service that can be used for all clients.
  • SWS is a single web service that can be used for all vendors.
  • SWS is a single web service that can be used for all data.
  • SWS provides a consistent configuration framework for all clients and vendors.

left to right direction
skinparam sequenceArrowThickness 3
Title: The SWS Alternative! Web Service Sprawl!

Package "PeopleSoft" {


  rectangle "Terms" as rec.terms #FFD3B0
  rectangle "Student Bio data" as rec.students #FFD3B0
  rectangle "Student Grades" as rec.grades #FFD3B0
  rectangle "Student Enrollment" as rec.se #FFD3B0
  rectangle "Invoices" as rec.invoices #FFD3B0

  ' package "Bespoke Web Services" {
  ' rectangle "Terms\nWeb Service" as ib.rec.terms #FF6969
  ' rectangle "Student Bio data\nWeb Service" as ib.rec.students #FF6969 
  ' rectangle "Student Bio data\nWeb Service\nVersion 2" as ib.rec.students2 #FF6969 
  ' rectangle "Student Grades\nWeb Service" as ib.rec.grades #FF6969
  ' rectangle "Student Enrollment\nWeb Service" as ib.rec.se #FF6969
  ' rectangle "Student Enrollment\nWeb Service \nVersion 2" as ib.rec.se2 #FF6965
  ' rectangle "Invoices\nWeb Service" as ib.rec.invoices #FF6969
' }

  ' ib.rec.terms <-- rec.terms
  ' ib.rec.students <-- rec.students
  ' ib.rec.students2 <-- rec.students
  ' ib.rec.grades <-- rec.grades
  ' ib.rec.se <-- rec.se
  ' ib.rec.se2 <-- rec.se
  ' ib.rec.invoices <-- rec.invoices

  rectangle "SWS - Web Service\n\n*Single Web Service\n*Configuration Based\n* No Hard Coding" as ib.sws #limeGreen
  ib.sws <-[#limeGreen,thickness=3,dashed]- rec.terms
  ib.sws <-[#limeGreen,thickness=3,dashed]- rec.students
  ib.sws <-[#limeGreen,thickness=3,dashed]- rec.grades
  ib.sws <-[#limeGreen,thickness=3,dashed]- rec.se
  ib.sws <-[#limeGreen,thickness=3,dashed]- rec.invoices


}


rectangle "LMS Integration Partner" as client #A6D0DD
rectangle "Student Success\nIntegration Partner" as client2 #A6D0DD
rectangle "Payment\nIntegration Partner" as client3 #A6D0DD

' client <-- ib.rec.terms
' client <-- ib.rec.students
' client <-- ib.rec.grades
' client <-- ib.rec.se


' client2 <-- ib.rec.students2
' client2 <-- ib.rec.grades
' client2 <-- ib.rec.se2

' client3 <-- ib.rec.invoices

client <-[#limeGreen,thickness=3,dashed]- ib.sws: GET
client2 <-[#limeGreen,thickness=3,dashed]- ib.sws: GET
client3 <-[#limeGreen,thickness=3,dashed]- ib.sws: GET

2 - Installing SWS

Installation and Patching Instructions

This section will describe how to install SWS in your PeopleSoft instance.

2.1 - Installing SWS

Detailed instructions on installing SWS into your PeopleSoft environment.

General Notes

Most PeopleSoft implementations have a migration path starting in a dev environment that flows to production. Every organization does this slightly differently. For this document, we assume you have a development flow that looks like the following.

  1. Development
  2. Test
  3. Production

We recommend starting in a development environment first. Then, follow your normal production migration procedure to push this to your production systems.

Import Project from File

When importing into your development environment, you will import the delivered project from a file. For subsequent databases like test and production, you should use the standard PeopleSoft migration using “Project Copy to Database.”

  • Login to your development instance using application designer.
  • Import the project from the file.
    • Inspect the upgrade windows to ensure there are no errors or warnings.
  • Application Designer:
    • Tools -> Copy Project -> From File
    • Select the project file. Always use the latest dated project file.
  • Note that “Compile PeopleCode after Import” is checked. You can also compile the project PeopleCode after the import. This is a good check to ensure that all objects imported correctly. There are times when the application can crash or skip things. Make sure the compile does NOT show any errors.
  • Next build the new tables
    • Make sure the Application Designer “Project Output window” does NOT show any errors.

Import Setup Data

The product comes with some configuration data to give some examples of working web services. These are mostly queries on basic PeopleTools tables that will exist in any PeopleSoft Application (Finance, Campus, HCM, etc). You are not required to import these configurations into your system. However, it is recommended that you install the CHG roles for development to gain a working understanding of the product.

Note: You will need to change the path in this script to match your environment

SET LOG C:\temp\SAMPLE_DATA_IMPORT.LOG;
SET INPUT C:\temp\SWS_EXPORT_config.DAT;
IMPORT C_SWS_CONF_PAR;
IMPORT C_SWS_CONF_PL;
IMPORT C_SWS_CONF_TBL;
IMPORT C_SWS_OUT_FLDS;

Grant Users Access

Now, you need to grant Admin and API users access to the setup pages and API. Please see the Security Setup Section in the User Documentation

Object Naming

The PeopleSoft project that delivers SWS is a 100% PeopleSoft bolt-on. No delivered PeopleSoft objects are modified. All

All PeopleSoft objects delivered with SWS

  • Object Prefix: CHG_
    • Exceptions:
      • Record Prefix - C_

List of SWS Installed Objects

Below is the list of objects that will be imported.

  • Record: C_PQL_PL_EDIT
  • Record: C_SWS_CONF_PAR
  • Record: C_SWS_CONF_PL
  • Record: C_SWS_CONF_TBL
  • Record: C_SWS_OUT_FLDS
  • Record: C_SWS_PL_EDIT
  • Record: C_SWS_REC_WL
  • Record: C_SWS_RUN_LOG
  • Record: C_SWS_WORK
  • Field: CHG_CACHE_MINUTES
  • Field: CHG_COPY_TO
  • Field: CHG_DEBUG_MODE
  • Field: CHG_DELETE_CONFIRM
  • Field: CHG_DE_INCL_META
  • Field: CHG_DE_OUT_FLDNAME
  • Field: CHG_DE_PATH
  • Field: CHG_ENCODING
  • Field: CHG_FIELD_NAMES
  • Field: CHG_MAX_LENGTH
  • Field: CHG_PARAM_DFLT_VAL
  • Field: CHG_PARM_DATA_TYPE
  • Field: CHG_PARM_NAME
  • Field: CHG_PARM_SEQNO
  • Field: CHG_PARM_TYPE
  • Field: CHG_PSOFT_QL_TEXT
  • Field: CHG_REQ_TYPE
  • Field: CHG_ROW_COUNT
  • Field: CHG_ROW_LIMIT
  • Field: CHG_SWS_MISC_GRP
  • Field: CHG_SWS_PQL_SETUP
  • Field: CHG_SWS_SQL_SETUP
  • Field: CHG_WAS_SUCCESS
  • Translate: CHG_ENCODING-CSV-1901-01-01
  • Translate: CHG_ENCODING-JSON-1901-01-01
  • Translate: CHG_ENCODING-XML-1901-01-01
  • Translate: CHG_PARM_DATA_TYPE-BOOL-1901-01-01
  • Translate: CHG_PARM_DATA_TYPE-INT-1901-01-01
  • Translate: CHG_PARM_DATA_TYPE-NUMB-1901-01-01
  • Translate: CHG_PARM_DATA_TYPE-STR-1901-01-01
  • Translate: CHG_PARM_TYPE-H-1901-01-01
  • Translate: CHG_PARM_TYPE-P-1901-01-01
  • Translate: CHG_PARM_TYPE-Q-1901-01-01
  • Translate: CHG_REQ_TYPE-P-1901-01-01
  • Translate: CHG_REQ_TYPE-S-1901-01-01
  • Page: CHG_SWS_ADMIN
  • Page: CHG_SWS_CONF_TBL
  • Page: CHG_SWS_REC_WL
  • Page: CHG_SWS_TEST
  • Menu: CHG_TOOLS
  • Component: CHG_SWS_CONF_TBL-GBL
  • Component: CHG_SWS_REC_WL-GBL
  • Record PeopleCode: C_SWS_REC_WL-LASTUPDDTTM-SavePreChange
  • Record PeopleCode: C_SWS_WORK-SETUP-FieldFormula
  • Role: CHG_SWS_ADMIN
  • Role: CHG_SWS_USER
  • SQL Object: C_PQL_PL_EDIT-2
  • SQL Object: C_SWS_PL_EDIT-2
  • Message Definition: CHG_EXPORTER_PARAMS
  • Message Definition: CHG_GENERIC
  • Message Definition: CHG_SWS_PARAMS
  • Message Definition: IB_REST_STUB
  • Page PeopleCode: CHG_SWS_TEST-Activate
  • Component PeopleCode: CHG_SWS_CONF_TBL-GBL-PostBuild
  • Component PeopleCode: CHG_SWS_CONF_TBL-GBL-PreBuild
  • Component PeopleCode: CHG_SWS_CONF_TBL-GBL-SavePostChange
  • Component PeopleCode: CHG_SWS_CONF_TBL-GBL-SavePreChange
  • Component Record PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_CONF_PAR-RowInit
  • Component Record PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_CONF_TBL-SearchInit
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_CONF_PAR-CHG_PARM_TYPE FieldChange
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_CONF_TBL-CHG_DE_PATH FieldChange
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_CONF_TBL-CHG_REQ_TYPE FieldChange
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_WORK-CHG_COPY_TO FieldChange
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_WORK-COPY_PB FieldChange
  • Component Record Field PeopleCode: CHG_SWS_CONF_TBL-GBL-C_SWS_WORK-DELETE_PB FieldChange
  • HTML Catalog: CHG_SWS_HTTP-4
  • Permission List: CHG_SWS_ADMIN
  • Permission List: CHG_SWS_USER
  • Portal Registry Structure: EMPLOYEE-C-CHG_SWS_CONF_TBL
  • Portal Registry Structure: EMPLOYEE-C-CHG_SWS_REC_WL
  • Portal Registry Structure: EMPLOYEE-F-CHG_CUSTOM
  • URL Definitions: C_SWS_DOC
  • Application Package: CHG_CONSTANTS-CHG_CONSTANTS-.
  • Application Package: CHG_ENCODING_TOOLS-CHG_ENCODING_TOOLS-.
  • Application Package: CHG_HTTP-CHG_HTTP-.
  • Application Package: CHG_IB-CHG_IB-.
  • Application Package: CHG_SWS-CHG_SWS-.
  • Application Package: CHG_UTILS-CHG_UTILS-.
  • Application Package: PsoftQL-CHG_SWS-:
  • Application Package PeopleCode: CHG_CONSTANTS-http
  • Application Package PeopleCode: CHG_ENCODING_TOOLS-JSONHelper
  • Application Package PeopleCode: CHG_ENCODING_TOOLS-XMLToPsoft
  • Application Package PeopleCode: CHG_ENCODING_TOOLS-constants
  • Application Package PeopleCode: CHG_ENCODING_TOOLS-psoftToJSON
  • Application Package PeopleCode: CHG_ENCODING_TOOLS-psoftToXML
  • Application Package PeopleCode: CHG_HTTP-URLParser
  • Application Package PeopleCode: CHG_HTTP-constants
  • Application Package PeopleCode: CHG_IB-IBRequest
  • Application Package PeopleCode: CHG_IB-IBResponse
  • Application Package PeopleCode: CHG_IB-constants
  • Application Package PeopleCode: CHG_SWS-PsoftQL-constants
  • Application Package PeopleCode: CHG_SWS-PsoftQL-fieldCriteria
  • Application Package PeopleCode: CHG_SWS-PsoftQL-request
  • Application Package PeopleCode: CHG_SWS-PsoftQL-requestRecord
  • Application Package PeopleCode: CHG_SWS-SWSConfig
  • Application Package PeopleCode: CHG_SWS-SWSHandler
  • Application Package PeopleCode: CHG_SWS-constants
  • Application Package PeopleCode: CHG_UTILS-Field
  • Application Package PeopleCode: CHG_UTILS-RecordHelper
  • Application Package PeopleCode: CHG_UTILS-debugLogger
  • Service: CHG_SWS
  • Service Operation: CHG_SWS_GET
  • Service Operation: CHG_SWS_PSOFTQL_POST
  • Service Operation Handler: CHG_SWS_GET-REQUESTHDLR
  • Service Operation Handler: CHG_SWS_PSOFTQL_POST-REQUESTHDLR
  • Service Operation Version : CHG_SWS_GET-v1
  • Service Operation Version : CHG_SWS_PSOFTQL_POST-v1
  • Service Operation Routing: GENERATED12273628-1900-01-01
  • Service Operation Routing: GENERATED60932339-1900-01-01
  • Logical Schema: CHG_SWS-PARAMS-V1
  • XML Schema: CHG_SWS-PARAMS-V1-XML
  • Document Schema: CHG_SWS-PARAMS-V1
  • Json Schema : CHG_SWS-PARAMS-V1-JSON
  • Html Schema : CHG_SWS-PARAMS-V1-HTML

2.2 - Patching SWS

In this article, we will discuss how SWS patching and bug fixes work.

Patches for SWS include all previously delivered objects, even if they have not changed. This ensures that you are brought completely up to date, even if your organization missed a patch. We do NOT deliver “delta projects” by design.

Cedar Hills Group completes development in our own sandboxes. We rely heavily on shared libraries that are used across different projects and clients that have their own code releases. The only way to be 100% confident that we deliver high-quality software patches is to deliver a full file with all the objects. This is similar in spirit to how Oracle delivers PeopleSoft patches by delivering a “demo” database that is the source of truth for a particular software PUM. If you need a “delta”/change-only application designer project, we have a procedure below on how to generate that.

The most straightforward way to mark patches is by the date in YYYY-MM-DD format. This represents the date the code was packaged from our development environment. The patch is in the Project comments of the Application Designer project CHG_SWS_V2.

You will be able to download the releases from the private GitHub repository. There will be a zip file resembling the release date like YYYY-MM-DD. You should always get the most recent version available from that list unless you have been directed otherwise by Cedar Hills Group, Inc.

Generate Delta Project

If your change control team needs a “change only” file for production migration of patches, that can easily be generated during the patching installation process. The procedure is below and must be done prior to installing the patch.

  • Place the application designer project on a drive where the PeopleTools Application Designer can access it.
  • In Application Designer:
    • Tools β†’ Compare and Report β†’ From File
      • If asked about replacing the existing project, choose to replace
    • Let the compare run
    • This will create a project in the source system where the upgrade flags and compare flags on the project will be set to the delta values between what we deliver and what you have in your target database.
    • File β†’ Save Project As: New Delta Project Name per your institutional standards.
    • The project “upgrade” flags will be set with items that have changed. You can delete the objects from the project that have not changed.

If you realize that you need a delta project after already installing the patch in your source database, you can perform a comparison from DEV to TEST or TEST to PROD to determine what objects have changed and delete any unchanged objects from the project.

Patch Installation Instructions

  • Download the latest YYYY-MM-DD code archive ZIP file from the New Installation section above.
  • Please unzip the file to a location that the Application Designer will be able to access.
  • There are two projects included in the attached zip. For patching, you will only import one of these projects.
  • In your DEV instances, import Project: CHG_SWS_V2
  • If there are special instructions for a patch, they will be listed in the subsection below.

2.3 - Release Notes

2020-01-02

  • Version 1.0 release

2023-06-10

  • Version 2 Release
    • Major changes in functionality. Merging PsoftQL functionality that was never publicly marketed before with the version 1 SWS into one platform.

2023-09-26

  • Small fix for automatic prompt table lookups. If no DESCR field is found, we now look for the first non-key character field on the prompt table that has a non-null value and return that. This supports prompt tables that have no DESCR field but have other fields that can be used as a description.

2024-05-01

  • Added some missing code to the project during last build.

2026-01-21

  • Add support for basic Aggregate functions in PsoftQL queries.
    • Only One record is supported and no support for Having clause currently.

2026-01-25

  • Added support for sorting results in PsoftQL queries.
    • Sort order can be ascending or descending.
    • Multiple sort fields are supported.
  • Fixed an issue with joining parent-child fields where the key field is a blank character and adding a “space” since PeopleSoft does NOT allow nulls in character fields.

2026-04-20

  • Added pagination support for SQL-type SWS configurations. Clients can now page through large result sets by passing the pageNumber and rowLimit URL query-string parameters. The response meta now includes pageNumber, rowLimit, and (when more rows remain) nextPageNumber. See Pagination for SQL endpoints for details.
    • This brings the SQL handler to parity with the PsoftQL handler on pagination.
    • Existing callers that send neither parameter continue to receive the same response they did before β€” the change is fully backward compatible.

3 - SWS Configuration

Documentation on how to configure SWS

The easiest and most secure way to expose your PeopleSoft data is by using SWS configured web services. This method gives the PeopleSoft admin control over what data is exposed to third parties and is a more secure model for most use cases.

  • A PeopleSoft power user:
    • Configures a new SWS configuration with either SQL or PsoftQL Syntax.
    • Defines parameters and filters
    • Grants security
    • Gives URL and Basic Auth Code to the integration partner

The integration partner ends up using a REST GET Service Operation called CHG_SWS. This web service checks security, resolves parameters, executes the SQL, encodes the data and returns it to the user.

Read more in the sections below.

3.1 - Configuration 🌟

How to Configure new SWS web services in the UI.

At the heart of SWS is a single PeopleSoft Service Operation called CHG_SWS_GET. This single service operation can serve infinite use cases. A PeopleSoft super-user who has read this documentation configures SWS to create new web services using a configuration page delivered as part of SWS. This configuration page lives inside your PeopleSoft database. It is the job of the CHG_SWS_GET service operation to interpret the configuration, enforce security, run SQL and export the results to the client.

This section discusses configuring this service in detail in this section as well as the security model and how to configure API client users.

Planning a new web service

Before configuring a new web service there are a few things to think through. We will cover them here at a high level and then go into detail in the subsequent sections. New web services are trivial to set up with SWS. However, without a little upfront thought, you can end up with some confusion for the users of your APIs.

URLs and Paths

In REST web services, the URL paths have an implied meaning and provide a hierarchy that should map to some logical structure of the underlying data it exposes. This paradigm is carried forward into SWS. The URL path is used to find the logic to run to export data over the web services. So you need to think through the URL path structure and how it will map to the SQL or PsoftQL statements you want to expose.

Thinking about the path hierarchy gives the API clients a good structure to reason about. When reading the paths from left to right, they start out broad and narrow down the request. A URL Path structure could be something like this:

  • {prefix}/person/{emplid}
    • This output information about a person and could possibly include some child data like phones, addresses, etc.
  • {prefix}/person/{emplid}/phone
    • This would output phones for a person. You might give this out to a team that needs to know phone numbers for a person and are not interested in other data or have security to see that other data.
  • {prefix}/person/{emplid}/address/
    • This would output addresses for a person.
  • {prefix}/person/{emplid}/address/{address_type}}
    • This would output a specific address type for a person.
  • {prefix}/security/users/{oprid}/
    • This would output general information on a specific OPRID.
  • {prefix}/security/users/{oprid}/roles/
    • This would output roles a user was a member of.

In those examples above, they are fairly generic paths. You can also have more specific paths that are more targeted to a specific use case. Let’s imagine that you have an integration with your SSO system. You need to expose some data that they need. Their requests do NOT seem to fit into any other categories that you have configured before. Perhaps those other SWS configurations expose too much data or sensitive data the SSO team should not see. They have very limited information and that team cannot use the generic paths above because the data is too broad. So you can create a more specific path for them. Let’s imagine that they need to know information about a person as well as to know if a person is active or not. You could create a path like this:

  • {prefix}/sso/person/{emplid}/
    • This may return general information about a person like name, email, etc.
  • {prefix}/sso/person/{emplid}/status
    • This may return a simple yes/no if the person is active or not.

In the case above, we “scoped” the SSO end points to the /sso/* path. If you had some other integration partners with very specific requirements then you could create a scoped path as well like /acmecorp/*. You can create as many paths as you need. You can also create paths that are not scoped to a specific integration partner. You can create paths that are scoped to a specific business process. For example, you could create a path for all the data needed for a specific business process like expense reports or invoices.

SWS is flexible. It provides no guidance on how to structure your paths. You can create as many paths as you need.

In SWS, the Path string is used to look up a configuration that ends up running some SQL and exporting the results to the client. So you need to think through the path structure and how it will map to the SQL or PsoftQL statements you want to run. You also configure security to these paths. As we will see shortly, a path maps to a specific SWS configuration row in the database. That configuration row has a security grid that allows you to specify what permission lists are allowed to run that configuration.

Syntax Types

There are two syntax types that SWS supports. When you configure a new web service you must choose one.

  • SQL - You can configure a SQL statement to to run.
  • PsoftQL - You can configure a PsoftQL statement to run.

When an HTTP client is calling a specific SWS path, the code in the web service handler looks up the configuration for that path. The configuration has a syntax type. The handler will use that syntax type to determine how to export data to the client. There are pros and cons to each syntax type. Here is a quick summary:

SWS Configuration TypesSQLPsoftQL
Accepts Client ParametersYesYes
Output JSONYesYes
Output XMLYesYes
Output CSVYesNo
Nested Data parent/child dataNoYes
PaginationYes (offset-based, via URL)Yes (built-in, via request body)
Data TranslationYes via SQL functions or AliasNo
Auto-Magic EFFDT LogicNone - Handled in SQLEFFDT Logic automatically handled
Auto-Magic EFF_STATUS LogicNone - Handled in SQLEFF_STATUS Logic automatically handled
Auto-Magic EFFSEQ LogicNone - Handled in SQLEFFSEQ Logic automatically handled
Auto-Magic Field ExportsNone - Handled in SQLAll record fields exported on table. No hard coding

Security

Security - Security is very important with PeopleSoft data as the database holds sensitive information. SWS is based on the PeopleSoft REST services. The only viable authentication mechanism for REST based services is “Basic Authentication” which is tied to a PeopleSoft OPRID and password stored in PSOPRDEFN. There is a very thorough document on REST Security in our Integration broker book. That should be your reference on how REST authentication works and the best practices. We assume you have read it and are following the best practices.

There is more explanation of security in the Security Page

Configuring a New Web Service

Configuring a new web service in SWS is easy and fast. You can deploy a new SQL or PsoftQL statement as a new web service in a few minutes. We will walk through all the configuration sections in this section. In later sections, we will show how to create different web services in order to give you some ideas of how this can be used.

Here is a screenshot of the SWS configuration page for a simple person example. Let’s go through each configuration at a high level. Then in later sections, we will drill into the detailed functionality offered.

Example SWS Configuration

Example SWS Configuration

In the example above we have configured the following PsoftQL statement to run at this path: /doc-example/person/{{emplid}}

{
  "isDebugMode": true,
    "records": [
        {
            "recordName": "PERSON",
            "sqlWhereClause": "  EMPLID = {{emplid}}",
            "excludeFields": [
                "BIRTHDATE",
                "BIRTHPLACE",
                "BIRTHCOUNTRY",
                "BIRTHSTATE",
                "DT_OF_DEATH"
            ]
        },
        {
            "recordName": "NAMES",
            "parentRecordName": "PERSON",
            "includeDescriptionsFor": [
                "NAME_TYPE"
            ],
            "criteriaFields": [
                {
                    "fieldName": "NAME_TYPE",
                    "fieldValue": "PRI"
                }
            ] 
        },
        {
            "recordName": "EMAIL_ADDRESSES",
            "parentRecordName": "PERSON",
            "includeDescriptionsFor": [
                "E_ADDR_TYPE"
            ]
        },
        {
            "recordName": "PERSONAL_PHONE",
            "parentRecordName": "PERSON",
            "includeDescriptionsFor": [
                "PHONE_TYPE"
            ]
        },
        {
            "recordName": "ADDRESSES",
            "parentRecordName": "PERSON",
            "includeDescriptionsFor": [
                "ADDRESS_TYPE"
            ]
        }
    ]
}

Let’s decode this configuration. We are configuring a PsoftQL statement to run. It is asking for this structure.

  • PERSON
    • NAMES
    • EMAIL_ADDRESSES
      • SWS will return all email address types since there are no filters
    • PERSONAL_PHONE
      • SWS will return all phone types since there are no filters
    • ADDRESSES
      • SWS will return all effective dated and active addresses since there are no filters. This is SWS auto-magic handling of effective dates and EFF_STATUS
  • Additionally, we are asking that we exclude some fields from the PERSON record. We do not want to expose these fields to the client.
    • BIRTHDATE
    • BIRTHPLACE
    • BIRTHCOUNTRY
    • BIRTHSTATE
    • DT_OF_DEATH
  • We are also making this a single response for a single EMPLID. We have the {{emplid}} variable in the path. That will be substituted into the PsoftQL statement at run time.
  • For NAMES, we are limiting it to only return the PRI name type.
  • We are also asking for several description fields to be included in the output. This is a feature of PsoftQL.
  • This SWS configuration is limited to a single permission list SWS_DOC_ACCOUNT, which is a service account we setup for this example.

We can then call this web service using an HTTP Request like this:

GET https://127.0.0.1:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/doc-example/person/FA0003
accept: application/json
authorization: Basic U1dTX0RPQ19BQ0NPVU5UOkRJUkdFLXNwZWxsaW5nLWphZGUtdml0aWF0ZQ==

The response will look like this:

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 1271
content-type: application/json; encoding=UTF-8
date: Mon, 25 Sep 2023 19:00:34 GMT
x-oracle-dms-ecid: 786dc482-39b0-4314-8f25-9a91bc734fdb-0000002a
x-oracle-dms-rid: 0
x-peoplesoftrequestid: ce514ec3-5bd5-11ee-adcb-d5409ab5d8fe
x-success: True

{
  "data": {
    "PERSON": {
      "objectType": "record",
      "objectName": "PERSON",
      "fields": [
        {
          "rowNumber": 1,
          "EMPLID": "FA0003",
          "LAST_CHILD_UPDDTM": "",
          "NAMES": {
            "objectType": "record",
            "objectName": "NAMES",
            "fields": [
              {
                "EMPLID": "FA0003",
                "NAME_TYPE": "PRF",
                "EFFDT": "1998-07-09",
                "EFF_STATUS": "A",
                "COUNTRY_NM_FORMAT": "001",
                "NAME": "Abban,Ali",
                "NAME_INITIALS": "",
                "NAME_PREFIX": "",
                "NAME_SUFFIX": "",
                "NAME_ROYAL_PREFIX": "",
                "NAME_ROYAL_SUFFIX": "",
                "NAME_TITLE": "",
                "LAST_NAME_SRCH": "ABBAN",
                "FIRST_NAME_SRCH": "ALI",
                "LAST_NAME": "Abban",
                "FIRST_NAME": "Ali",
                "MIDDLE_NAME": "",
                "SECOND_LAST_NAME": "",
                "SECOND_LAST_SRCH": "",
                "NAME_AC": "",
                "PREF_FIRST_NAME": "",
                "PARTNER_LAST_NAME": "",
                "PARTNER_ROY_PREFIX": "",
                "LAST_NAME_PREF_NLD": "1",
                "NAME_DISPLAY": "Ali Abban",
                "NAME_FORMAL": "Ali Abban",
                "NAME_DISPLAY_SRCH": "ALIABBAN",
                "LASTUPDDTTM": "",
                "LASTUPDOPRID": ""
              },
              {
                "EMPLID": "FA0003",
                "NAME_TYPE": "PRI",
                "EFFDT": "1998-07-09",
                "EFF_STATUS": "A",
                "COUNTRY_NM_FORMAT": "001",
                "NAME": "Abban,Ali",
                "NAME_INITIALS": "",
                "NAME_PREFIX": "Ms",
                "NAME_SUFFIX": "",
                "NAME_ROYAL_PREFIX": "",
                "NAME_ROYAL_SUFFIX": "",
                "NAME_TITLE": "",
                "LAST_NAME_SRCH": "ABBAN",
                "FIRST_NAME_SRCH": "ALI",
                "LAST_NAME": "Abban",
                "FIRST_NAME": "Ali",
                "MIDDLE_NAME": "",
                "SECOND_LAST_NAME": "",
                "SECOND_LAST_SRCH": "",
                "NAME_AC": "",
                "PREF_FIRST_NAME": "",
                "PARTNER_LAST_NAME": "",
                "PARTNER_ROY_PREFIX": "",
                "LAST_NAME_PREF_NLD": "1",
                "NAME_DISPLAY": "Ali Abban",
                "NAME_FORMAL": "Ms Ali Abban",
                "NAME_DISPLAY_SRCH": "ALIABBAN",
                "LASTUPDDTTM": "",
                "LASTUPDOPRID": ""
              }
            ]
          },
          "EMAIL_ADDRESSES": {
            "objectType": "record",
            "objectName": "EMAIL_ADDRESSES",
            "fields": []
          },
          "PERSONAL_PHONE": {
            "objectType": "record",
            "objectName": "PERSONAL_PHONE",
            "fields": [
              {
                "EMPLID": "FA0003",
                "PHONE_TYPE": "MAIN",
                "COUNTRY_CODE": "",
                "PHONE": "",
                "EXTENSION": "",
                "PREF_PHONE_FLAG": "Y"
              }
            ]
          },
          "ADDRESSES": {
            "objectType": "record",
            "objectName": "ADDRESSES",
            "fields": [
              {
                "EMPLID": "FA0003",
                "ADDRESS_TYPE": "PERM",
                "EFFDT": "1998-01-14",
                "EFF_STATUS": "A",
                "COUNTRY": "USA",
                "ADDRESS1": "10299 Placid Place",
                "ADDRESS2": "",
                "ADDRESS3": "",
                "ADDRESS4": "",
                "CITY": "New Market",
                "NUM1": "",
                "NUM2": "",
                "HOUSE_TYPE": "",
                "ADDR_FIELD1": "",
                "ADDR_FIELD2": "",
                "ADDR_FIELD3": "",
                "COUNTY": "",
                "STATE": "MD",
                "POSTAL": "21774",
                "GEO_CODE": "",
                "IN_CITY_LIMIT": "",
                "ADDRESS1_AC": "",
                "ADDRESS2_AC": "",
                "ADDRESS3_AC": "",
                "CITY_AC": "",
                "REG_REGION": "",
                "LASTUPDDTTM": "",
                "LASTUPDOPRID": ""
              }
            ]
          }
        }
      ]
    }
  },
  "errorMessages": "",
  "errors": "",
  "meta": {
    "rowCount": "1",
    "sqlIDExecuted": "71906a11-57b0-4831-b227-f5238ed1be58",
    "success": "True",
    "debugMessages": "",
    "QueryString": "NULL",
    "URLPath": "doc-example/person/FA0003",
    "finalSQL": "{\n    \"records\": [\n        {\n            \"recordName\": \"PERSON\",\n            \"sqlWhereClause\": \"  EMPLID = 'FA0003'\",\n            \"excludeFields\": [\"BIRTHDATE\",\"BIRTHPLACE\", \"BIRTHCOUNTRY\", \"BIRTHSTATE\", \"DT_OF_DEATH\"]\n        },\n        {\n            \"recordName\": \"NAMES\",\n            \"parentRecordName\": \"PERSON\"\n        },\n        {\n            \"recordName\": \"EMAIL_ADDRESSES\",\n            \"parentRecordName\": \"PERSON\"\n        },\n        {\n            \"recordName\": \"PERSONAL_PHONE\",\n            \"parentRecordName\": \"PERSON\"\n        },\n        {\n            \"recordName\": \"ADDRESSES\",\n            \"parentRecordName\": \"PERSON\"\n        }\n    ]\n}",
    "productVersion": "2023-06-07",
    "toolsVer": "8.58.07",
    "currentUser": "SWS_DOC_ACCOUNT",
    "responseDTTM": "2023-09-25 19:00:34.000000",
    "psftTransactionId": "ce514ec3-5bd5-11ee-adcb-d5409ab5d8fe",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "ServerDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessage": ""
  }
}

Configuration Options

Let’s cover each field on the SWS configuration page.

  • Unique Identifier: This is a system generated GUID that will be generated at save time. It is used to uniquely identify this configuration. You can not change this value and it should be safe to export to another database unlike a integer counter.
  • Description: This is the description of the configuration. This is for admin use and is used in PeopleSoft search records.
  • URL Path: This field is very important and will map to the full URL that a client will use to target your web service. This has to be unique across the database.
  • Active: Allows you to easily turn on or off the configuration.
  • Notes: This is a section for you to add notes about this web service. This might be internal notes or links to documentation or development tickets.
  • Request Format Type: PSOFT-QL or SQL - This defines what syntax SWS uses to query and return data.
    • If “Psoft-QL request format type was chosen then a long text box will show up called. Psoft-QL Text (JSON)
      • You enter the Psoft-QL that you want to run in response to a client request. This has a very particular format.
    • If “SQL” request format type was chosen then a long text box will show up called. SQL Statement Text
      • You enter the SQL that you want to run in response to a client request.
    • SQL Statement Text: This is the section where you enter the SQL statement that will run.
    • There is some special syntax for binding variables from the client request to the SQL statement. We will cover that detail in the next section.
  • Row Limit: Optional row limit. You can specify a non-zero number here and the handler will stop the output after that number of output rows has been encoded. Clients can override this at runtime by passing ?rowLimit=N on the URL, which also serves as the page size when the client paginates with ?pageNumber=N. See Pagination for SQL endpoints below.
  • Default Encoding: Used to set a default encoding scheme if the client does not include one in the request. All SWS web services automatically handle JSON, CSV and XML encoding.
  • Include metadata in Response: If checked then some additional information about the SWS request and response will be encoded. We will show some examples of this shortly.
  • (danger) Minutes to Cache Response: You can optionally configure to have the integration broker cache the response. This can be done for performance reasons if the SQL is very “expensive”. Please read the Caching article before enabling this. There are some hidden dangers.
  • Parameters Grid: This grid allows you to configure how the client passes parameters that are substituted into the SQL. If your SQL does not have any variables provided by the client, there will be no rows here. We will cover this in depth shortly.
  • Output Fields Grid: This will only show for a “SQL” Request Format Type. This grid is where you name the SQL columns that show up in the encoding section. For JSON and XML encoding, the values here will be the names of the properties and nodes. For CSV, these will be the header values.
  • Allowed Permission Lists Grid: This grid is where you configure SWS to tell what permission lists are authorized to run this SQL statement. There is a detailed security section below that documents how to use this and setup API client users.
  • Copy & Delete - Inside this group box are buttons to clone/copy the current configuration or to delete it.
  • DMS Export - Inside this group box is a generated DMS script template that can be used to export your configuration between databases.
  • HTTP Info Page - This page gives you both HTTP and curl examples on how to invoke the service operation. This removes the guesswork and speeds up the testing cycle.

Pagination for SQL endpoints

Any SQL-type SWS configuration can be paginated by the client with two optional URL query-string parameters. No configuration change is required on the SWS setup page β€” the parameters are applied automatically by the SWS handler when present.

Query parameterTypeDefaultMeaning
rowLimitintegerSetup-configured Row Limit valueThe maximum number of rows returned on a single page.
pageNumberinteger11-based page index. Page 1 returns rows 1–rowLimit, page 2 returns the next rowLimit rows, and so on.

Pagination is offset-based. The handler fetches the full result set from the database and skips the rows that belong to earlier pages before it encodes the response. This means every paginated call still pays the cost of the underlying query, so pagination is not a substitute for an efficient WHERE clause when working with very large tables.

Example

Fetch the third page of 20 users from a SQL-backed /sql/allusers configuration:

GET /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/sql/allusers?pageNumber=3&rowLimit=20
Accept: application/json
Authorization: Basic <token>

The response payload includes three additional meta fields so the client knows where it is in the result set:

{
  "data": [ /* ... 20 rows ... */ ],
  "errors": "",
  "meta": {
    "rowCount": "20",
    "pageNumber": "3",
    "rowLimit": "20",
    "nextPageNumber": "4",
    "success": "True"
  }
}
  • pageNumber and rowLimit echo the effective values used for this request.
  • nextPageNumber is present only when at least one more row remains after the current page. Its absence signals the last page.
  • For XML responses the same three fields appear under /response/meta/. For CSV responses they are emitted as the HTTP response headers x-pageNumber, x-rowLimit, and x-nextPageNumber (matching the existing x-rowCount, x-success, and other x-* headers used by CSV clients).

Backward compatibility

If a client sends neither pageNumber nor rowLimit, the SQL endpoint behaves exactly as it did before pagination was added: results are capped only by the setup-configured Row Limit field on the configuration row. Existing integrations do not need to change.

Ordering

Pagination relies on the natural ordering of the configured SQL statement. If the SQL does not include an ORDER BY clause, the database is free to return rows in any order, and that order can shift between calls β€” paging may then skip or duplicate rows. When you design a SQL configuration that clients will page through, add an explicit ORDER BY on a stable key.

3.2 - SWS Security Setup

How to setup users and security.

SWS has several layers of security for the web services. Additionally, security is required for a few custom components that are delivered with SWS that power-user use to configure SWS. We will discuss those here.

For SWS, there are two main areas of security.

  • API users who need to use call an SWS web service.
  • Security for Administrators to create and update SWS configurations.
    • This security is just needed by trusted users who will be configuring SWS web services.

SWS API User Security

SWS is based on PeopleSoft REST framework. The security mechanism that exists for REST is based on standard PeopleSoft PSOPRDEFN OPRIDs and passwords. We will walk through how this works and the best practices in this section. SWS is backed by a custom PeopleSoft REST Service.

For API users that need to invoke SWS web services, they need to be setup as a valid PeopleSoft user.

The technical design of SWS is actually structured around one PeopleSoft service operation called CHG_SWS_GET. All SQL statements that you deploy as a web service are handled by this one service operation and PeopleCode Handler. Therefore, all API users will need this base service operation as part of their security.

There are a few layers to security with SWS:

  • PeopleSoft OPRID and Password
    • The basic auth token provided must be a valid PeopleSoft OPRID and password. The PeopleSoft IB will authenticate the OPRID and password. If the OPRID is not valid or the password is incorrect, the IB will return an error.
  • The REST Service Account OPRID’s access to the Service Operation CHG_SWS_GET
    • The authenticated OPRID must have access to the CHG_SWS_GET service operation. If the OPRID does not have access, the IB will return an error.
  • The SWS Configuration Security setup for the Path.
    • The authenticated OPRID must have access to the SWS configuration for the path. If the OPRID does not have access, the SWS will return an error.
  • The configured SQL or PsoftQL could also have data security configured to limit what data is returned to the client.

@startuml

participant "HTTP Client" as client
participant "PeopleSoft IB" as psib
participant "SWS Handler" as pssws


client -> psib: HTTP Request\nBasic Auth Token
psib -> psib: Authenticate\nOPRID and Password

psib -> psib: Check Access\nTo Service Operation
psib -> pssws: Call Service Operation\nCHG_SWS_GET
pssws -> pssws: Lookup SWS\nConfiguration
pssws -> pssws: Is User Authorized?
pssws -> pssws: Convert Configuration into SQL
pssws -> pssws: Get Data
pssws -> client: Return Data

@enduml

The handler PeopleCode in the CHG_SWS_GET operation actually performs some additional checks on the current user to determine if they have access to execute the SQL statement at the path. Those permission lists are configured on the SWS setup table (COMPONENT: CHG_SWS_CONF_TBL). If an API user tries to invoke a SWS SQL statement and they do NOT have security, the SQL will NOT be run.

What is required to create an API User for SWS?

  • Create a new OPRID that represents the client using the application.
  • Give that OPRID a complex password. This password stored in PSOPRDEFN will be used in the authentication.
  • Give that OPRID access to the Service Operation CHG_SWS_GET
    • You can use the role CHG_SWS_USER for this purpose.
  • Give that user access to some other unique permission list that identifies it and that you can use to secure the SWS setup.
    • When the clients tries to trigger an SWS web service, the API User OPRID must have a permission list configured on the SQL statement. Each SWS SQL statement is tagged with permission lists that are allowed to execute it.

You can use the security objects we delivered as part of the project or use your own permission lists based on your own standard.

SWS API Delivered Security:

  • Permission List: CHG_SWS_USER
    • Service: CHG_SWS, Service Operation: CHG_SWS_GET
  • Role: CHG_SWS_USER
    • Permission List: CHG_SWS_USER

Example User ID and Basic Authentication Code

Let’s imagine we want to set up a test API user account to manually test web services.

  • Create a new OPRID called Z_TEST_API_USER
    • Create a complex randomly generated password. We will use proctor-consular-esther-hull-flood for this example.
    • The EMPLID can be set to nothing.
    • Set the other required fields to your system’s normal default values for a low-level non-privileged account.
  • Give OPRID Z_TEST_API_USER the role CHG_SWS_USER
  • Create a permission list called Z_TEST_API_USER. This will not have any real permission in the security tab. We will use it in the SWS configuration.
  • Create a new role called Z_TEST_API_USER and assign it the Z_TEST_API_USER permission list.
  • Grant user Z_TEST_API_USER the new Z_TEST_API_USER role.

Now that we have our OPRID and password we can generate an HTTP Basic Authentication token using the following scheme.

  • Concatenate the OPRID and password together with a colon. That gives us:
    • Z_TEST_API_USER:proctor-consular-esther-hull-flood
  • Base64 encode that concatenated string.
    • Z_TEST_API_USER:proctor-consular-esther-hull-flood –> Wl9URVNUX0FQSV9VU0VSOnByb2N0b3ItY29uc3VsYXItZXN0aGVyLWh1bGwtZmxvb2Q=
    • Many text editors and HTTP test clients like Postman have base64 encoding functions built in. There are also tools online that will do this but I would not trust those with my production passwords.
  • The base64 encoded output servers as the token and is used in an HTTP Basic authorization header in the following form:
    • Authorization: Basic Wl9URVNUX0FQSV9VU0VSOnByb2N0b3ItY29uc3VsYXItZXN0aGVyLWh1bGwtZmxvb2Q=

Security Best Practices

For these best practices, we are going to imagine three fictional internal systems that will be calling SWS web services that we can use in our example.

  • Stellar Wind - Internal Payment System
  • PRISM - Internal CRM system
  • MYSTIC - Internal Marketing System

Based on those three systems, we will show you the recommended security setup for these API users. Your security team may vary this depending on your own standards.

  • For each client or external system calling your web service, create a new and unique OPRID. Do not reuse a super user or real user account for this. Create a new account that only has the minimum API security and no PIA login ability.
    • For our three internal systems that may look like:
      • OPRID: Z_STELLAR_WIND_API_USER
      • OPRID: Z_PRISM_API_USER
      • OPRID: Z_MYSTIC_API_USER
    • Do not share API user accounts across systems.
      • Shared accounts make password rotation nearly impossible since you have to coordinate with more than one group.
      • Shared accounts also make auditing more difficult.
    • Having separate accounts makes it easy for you to shut off one system and not impact the others.
  • Each API User should have a complex password that is generated by a password manager.
    • Store your passwords in some sort of password database.
    • Do NOT email passwords to users. Emails persist forever.
  • The API Users should store the password in a secure location that easily facilitates password rotation.
    • The password should NOT be hard coded in the source code.
    • This could be an environment variable or something more advanced like Hashicorp Vault
  • Each API user may have a distinct permission list that identifies it and that you can use in SWS. The API user may have access to other PeopleSoft APIs. We often recommend creating a permission list and a role that is equal to the OPRID. Your security standard may be different. We find this simple model for API client OPRIDs is effective and allows visibility into what permissions an API user has. (OPRID = ROLENAME = CLASSID). API Clients tend to have very specific permissions that are not shared across other users.
    • For our three internal systems that may end up looking like this:
      • OPRID: Z_STELLAR_WIND_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_STELLAR_WIND_API_USER
          • Permission List: Z_STELLAR_WIND_API_USER
      • OPRID: Z_PRISM_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_PRISM_API_USER
          • Permission List: Z_PRISM_API_USER
      • OPRID: Z_MYSTIC_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_MYSTIC_API_USER
          • Permission List: Z_MYSTIC_API_USER

Our Integration Broker book has a detailed section on how REST Security in PeopleSoft works. That is great reference.

SWS Administrator Security

For users who will be maintaining SWS configuration, they need access to the setup component for SWS. You can use the permission list and role that we delivered with the project or use one that works with your security standard.

SWS Administrator Delivered Security:

  • Permission List: CHG_SWS_ADMIN
    • MENU: CHG_TOOLS, COMPONENT: CHG_SWS_CONF_TBL
    • Service: CHG_SWS, Service Operation: CHG_SWS_GET
  • Role: CHG_SWS_ADMIN
    • Permission List: CHG_SWS_ADMIN

Research Queries

This SQL will find the users who have access to the SWS Web Services.

  • Users who have access to Service Operation: CHG_SWS_PSOFTQL_POST should be extremely limited.
-- Find Users who have access to the 
-- SWS Web Services

SELECT
  A.OPRID ,
  B.ROLENAME ,
  C.CLASSID ,
  O.IB_OPERATIONNAME,
  AU.CLASSID
FROM
  PSOPRDEFN A ,
  PSROLEUSER B ,
  PSROLECLASS C ,
  PSOPERATION O,
  PSAUTHWS AU
WHERE
  A.OPRID = B.ROLEUSER
  AND B.ROLENAME = C.ROLENAME
  AND O.IB_OPERATIONNAME LIKE 'CHG_SWS%'
  AND O.IB_OPERATIONNAME = AU.IB_OPERATIONNAME
  AND AU.CLASSID = C.CLASSID
ORDER BY
  A.OPRID,
  B.ROLENAME;

Find users who can access CHG_SWS_GET and the paths they can execute

--- Find users who can access CHG_SWS_GET and the paths they can execute
SELECT
  A.OPRID ,
  B.ROLENAME ,
  C.CLASSID ,
  O.IB_OPERATIONNAME,
  SWSC.CHG_DE_PATH
FROM
  PSOPRDEFN A ,
  PSROLEUSER B ,
  PSROLECLASS C ,
  PSOPERATION O,
  PSAUTHWS AU, PS_C_SWS_CONF_TBL SWSC, PS_C_SWS_CONF_PL SWSPL
WHERE
  A.OPRID = B.ROLEUSER
  AND B.ROLENAME = C.ROLENAME
  AND O.IB_OPERATIONNAME = 'CHG_SWS_GET'
  AND O.IB_OPERATIONNAME = AU.IB_OPERATIONNAME
  AND AU.CLASSID = C.CLASSID
  AND SWSPL.CLASSID = AU.CLASSID
  AND SWSC.GUID = SWSPL.GUID;

3.3 - SWS Caching

Advanced Topic - Caching Responses - Read Carefully!
  • Minutes to Cache Response: You can optionally configure to have the integration broker cache the response. This can be done for performance reasons if the SQL is very “expensive”.

SWS can tell the RESTListeningConnector servlet to cache the response. This caching is provided by the built-in caching mechanism in PeopleTools REST functionality. It can speed up requests for expensive SQL where the result is not expected to change often. There are some known caveats β€” see the PeopleSoft REST Caching integration broker book section before using this.

3.4 -

@startuml

participant "HTTP Client" as client
participant "PeopleSoft IB" as psib
participant "SWS Handler" as pssws


client -> psib: HTTP Request\nBasic Auth Token
psib -> psib: Authenticate\nOPRID and Password

psib -> psib: Check Access\nTo Service Operation
psib -> pssws: Call Service Operation\nCHG_SWS_GET
pssws -> pssws: Lookup SWS\nConfiguration
pssws -> pssws: Is User Authorized?
pssws -> pssws: Convert Configuration into SQL
pssws -> pssws: Get Data
pssws -> client: Return Data

@enduml

4 - PeopleSoft Query Language (PsoftQL)

This documents the detailed PsoftQL - PeopleSoft Query Language

PsoftQL (PeopleSoft Query Language) is a request structure that is used by SWS to “ask” for data. This is similar in spirit to GraphQL but it has a syntax that is targeted specifically for asking for PeopleSoft data in a minimal format.

4.1 - Service Operation CHG_SWS_PSOFTQL

Documentation on the CHG_SWS_PSOFTQL web service. This is a more advanced web service that will will give to very limited and trusted integration partners or when prototyping new web services.

The CHG_SWS_PSOFTQL service operation is a web service that allows a privileged and trusted client to “ask” for any number of tables in PeopleSoft using PsoftQL Syntax. It can return structured JSON or XML data that mimics the PeopleSoft data structure. It is similar in spirit to GraphQL but only works inside a PeopleSoft database. The response body structure will vary based on the input request parameters.

This web service puts the responsibility on the integration client to know what they are asking for. The SWS GET Handler uses similar concepts but hides the complexity of the PeopleSoft data and is more secure.

Using the Service

Unlike the standard GET Service where the request syntax is hidden in the configuration table, this web service requires the client to know the PeopleSoft record structure and how to craft a request. The service takes a PsoftQL Syntax request as the body of the HTTP Post.

Let’s look at some examples and it will make more sense. We are using PsoftQL syntax to ask for tables. The request body is JSON or XML. The response body is JSON or XML. The response body structure will vary based on the input request parameters. All tables that are requested must be “whitelisted” to the API user. See the security section below for more details.

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic basic ....token....
Content-Type: application/json
Host: your-ib-host.com

{
    "isDebugMode": true,
    "includeFieldTypes": true,
    "includeAllDescriptions": true,
    "includeKeyFieldIndicators": true,
    "records": [
    {
        "recordName": "PSOPRDEFN",
        "includeDescriptionsFor": [],
        "excludeFields": []
    }
    ]
}

Here is the same request but in XML form.

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic basic ....token....
Content-Type: application/xml
Host: your-ib-host.com


<?xml version="1.0" encoding="UTF-8" ?>
<request>
    <isDebugMode>false</isDebugMode>
    <rowLimit>20</rowLimit>
    <noEffectiveStatusLogic>false</noEffectiveStatusLogic>
    <noEffectiveDateLogic>false</noEffectiveDateLogic>
    <includeFieldTypes>false</includeFieldTypes>
    <includeAllDescriptions>false</includeAllDescriptions>
    <includeKeyFieldIndicators>false</includeKeyFieldIndicators>
    <includeAllFieldLabels>false</includeAllFieldLabels>
    <effectiveDateOverride></effectiveDateOverride>
    <records>
        <record>
            <recordName>PSOPRDEFN</recordName>
        </record>
    </records>
</request>

Intended Use Cases

  • This web service can replace many other “GET” operations and can provide more flexibility for different clients. This can accommodate custom client tables to be included in standard GET operations by just changing the request body. You should try to use this operation as a GET first. If it cannot accommodate the request then you can develop a custom operation.
  • Pulling out “pick lists” or “prompt tables” values. For example, a list of subjects, locations, etc.
  • Pulling out “XLAT” values
  • Generating “Changes since x hours ago” queries. Many institutions may have large numbers of tables that you only want to pull out changes since the last sync. In this situation, you need some way to query “what has changed in the last hours” and then only pull those objects that have changed. You can use this web service and the “where clause” functionality to craft a query to find these and then potentially use other web services to only pull those objects.

Security Considerations

Data security is an important component of an enterprise. This web service technically can extract any PeopleSoft table or view out of the client system. 99.99% of institutions will not like this and want some sort of restriction on what tables this web service can pull. We have developed a client-owned “Whitelist” table. This is a simple listing of tables that this web service can be allowed to pull from the client system.

There is a PeopleSoft page where this is configured.

  • Menu Name: CHG_TOOLS
  • Component Name: CHG_SWS_REC_WL
  • Record Name: C_SWS_REC_WL

The table also allows you to define a different whitelist for different API users. For example, we might have different API clients using this API that need different sets of data. The API authentication token is tied to a PeopleSoft user (Permission List). Therefore, you define what records (tables) each permission list should be allowed to see.

Here is an example where there are a few tables that are whitelisted. In this case, these are the only tables that can be pulled by this web service.

For these advanced use cases, it is often helpful for the client to be able to see what is whitelisted. In that case, you should “whitelist” the “whitelist” table and they can query it.

The whitelist table can be queried using this payload.

{
    "request": {
        "isDebugMode": false,
        "includeFieldTypes": false,
        "records": [
            {
                "recordName": "C_SWS_REC_WL"
            }
        ]
    }
}

That query above will return all tables that have been whitelisted for all permission lists NOT just limited to what the user will see. This version will limit what the user has access to but you need to change the "fieldValue": "CHG_SWS_USER" to something that matches your permission list.

{
    "isDebugMode": true,
    "records": [
    {
        "recordName": "C_SWS_REC_WL",
        "criteriaFields": [
        {
            "fieldName": "CLASSID",
            "fieldValue": "CHG_SWS_USER"
        }
        ]
    }
    ]
}

Features

  • This web service is JSON or XML-based.
  • The client can request a PeopleSoft record be returned and can include a where clause in various forms.
    • Child and Grandchildren table nesting is supported using specific request syntax.
    • A table in PeopleSoft is called a “record”. That is an object in the PeopleTools code. The field structure of the record will be synced to a database table with the same name as the record with “PS_” prefix for application tables and “PS” for PeopleTools table. All parameters for this web service refer to the record name and not the database table name.
  • All record fields are exported unless you specifically ask for certain table fields to NOT be included. This can be useful on tables that have sensitive data (SNN, Salary) that you may not be using, and you don’t want logged across the infrastructure.
  • The service supports adding human-readable code descriptions to the output. For example, if a CAMPUS code is “SDIEG” a description attribute may be returned that says “San Diego”.
  • The service supports pagination to extract large amounts of data.
  • Effective data logic is automatically handled. If the parent has the EFFDT field then all children requested will use that EFFDT.
  • Records that have the EFF_STATUS field pull only the active value. This happens automatically but the request parameter is highly configurable.

Assumptions & Notes

  • The client is a very trusted service account (PeopleSoft OPRID). This web service is not meant to be called from a web browser by some end user or javascript. This is something that should be called from immutable server code.
  • The base response JSON structure is the same. All data is returned inside a data element. The JSON structure of what is returned nested inside the data element is entirely based on the request as the response data mimics that database/record structure. This will make more sense when you look at the examples.
  • When requesting child and grandchild records, the logic looks at all fields on the parent. If the child table has a key field matching the field name then that value is used to pull data for this child. This is dynamically generated based on the PeopleSoft metadata. This allows you to ask for “children” that are NOT true children in PeopleSoft. For example, you may have a FACILITY_TBL that has a LOCATION code validated by the LOCATION_TBL. You can ask for the LOCATION_TBL as a child, and it will include the row from the LOCATION_TBL that matches the LOCATION entered on the FACILITY_TBL.
    • This service does not validate the parent-child relationships in the input parameters. You can submit bad requests. It is assumed you are working with someone who can tell you what the correct parent-child relationship is of tables. It gives you plenty of room to make a mistake because there is really no place in PeopleSoft to map parent-child relationships.
    • There can be any number of nesting of parent/child/grandchild.
  • Effective dated logic is automatically applied to each record. You can pass in an optional ISO 8601 date (YYYY-MM-DD) like "effectiveDateOverride": "2021-12-25" for a record. This is optional. The current date will be used for all effective dating logic if data is not specified.
    • If you need all historical records, there is an optional parameter to NOT apply effective dated logic.
  • You can only have one root record.
  • Row Limits - If a row limit is not specified, then only the 1st 50 rows are returned and the remaining rows can be retrieved using pagination.
  • Pagination is supported and enforced for large tables.
  • Effective Status
    • This web service only pulls back records where EFF_STATUS = 'A'. Not all records have the EFF_STATUS field. The web service will apply this automatically. It can be turned off if you need values that are inactive.

4.2 - PsoftQL Syntax 🌟

Detailed PsoftQL syntax documentation. PsoftQL serves as the “language” of SWS

The PeopleSoft Query Language (PsoftQL) is a powerful tool that allows users to extract information from PeopleSoft applications with SWS. PsoftQL is a request syntax used by the SWS to extract data from PeopleSoft. PsoftQL syntax is used when configuring SWS services.

PsoftQL is a succinct language that provides fine-grain control over what data is returned from a PeopleSoft application. It allows users to specify key parameters that determine the exact data that should be returned, making it an indispensable tool for those who seek specific information from their PeopleSoft applications.

By default, PsoftQL has reasonable defaults that accommodate most requests. However, advanced users can manipulate the various knobs and dials available to override the default behavior and access more detailed information. For instance, one can hide fields or join non-standard fields across tables to the query, ensuring that the data returned meets their specific needs.

Understanding base PsoftQL syntax is essential for working with SWS. The syntax is relatively easy to learn and can be used to access any data stored in PeopleSoft applications.

This document will cover every piece of the syntax.

First, we’ll demonstrate a few basic examples using the default behavior of SWS and PsoftQL syntax. This will prove that you don’t need to know every parameter to extract key information with SWS. After covering the details in the documentation, we’ll explore advanced usage later on.

Simple Examples

We will use JSON syntax here but XML syntax is also supported.

One Table

First, ask for the first 3 rows from the PSROLEDEFN table which defines roles in PeopleSoft security.

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ..redacted..
Content-Type: application/json
Host: dev.ib.cedarhillsgroup.com
Content-Length: 84

{
  "rowLimit": 3,
  "records": [
    {
      "recordName": "PSROLEDEFN"
    }
  ]
}

The response we get back from the web service is detailed and exports all fields.

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 795
content-type: application/json; encoding=UTF-8
date: Tue, 06 Jun 2023 05:21:32 GMT

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        {
          "rowNumber": 1,
          "ROLENAME": "ACM Administrator",
          "VERSION": 1,
          "ROLETYPE": "U",
          "DESCR": "ACM Administrator",
          "QRYNAME": "",
          "ROLESTATUS": "A",
          "RECNAME": "",
          "FIELDNAME": "",
          "PC_EVENT_TYPE": "",
          "QRYNAME_SEC": "",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "",
          "ALLOWNOTIFY": "Y",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2020-02-19-13.49.14.223597",
          "LASTUPDOPRID": "PPLSOFT"
        },
        {
          "rowNumber": 2,
          "ROLENAME": "ADS Designer",
          "VERSION": 1,
          "ROLETYPE": "U",
          "DESCR": "ADS Designer",
          "QRYNAME": "",
          "ROLESTATUS": "A",
          "RECNAME": "",
          "FIELDNAME": "",
          "PC_EVENT_TYPE": "",
          "QRYNAME_SEC": "",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "Role for Application Dataset Designer",
          "ALLOWNOTIFY": "Y",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2020-02-19-13.49.14.223642",
          "LASTUPDOPRID": "PPLSOFT"
        },
        {
          "rowNumber": 3,
          "ROLENAME": "AG Composer Administrator",
          "VERSION": 1,
          "ROLETYPE": "U",
          "DESCR": "AG Composer Administrator",
          "QRYNAME": "",
          "ROLESTATUS": "A",
          "RECNAME": "",
          "FIELDNAME": "",
          "PC_EVENT_TYPE": "",
          "QRYNAME_SEC": "",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "This role provides access to the setup pages to define, manage, and maintain Activity Guide Composer templates.",
          "ALLOWNOTIFY": "Y",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2019-05-22-09.34.01.102262",
          "LASTUPDOPRID": "PPLSOFT"
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-05.21.33.000000",
    "psftTransactionId": "ffb423b7-0429-11ee-9a28-e332b6feab8c",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 1,
  "apiVersion": "2023-03-14",
  "nextPageNumber": 2
}

Please note that SWS is NOT hard coded in any way with the fields that exist. SWS reads the PeopleTools meta-data to determine what fields exist on the record. So if Oracle changes the fields or you add custom fields those are automatically picked up.

Adding Some Metadata

Let’s go a bit deeper. Let’s say that your external integration partner needs to know the field label and also decode any sort of XLATs or prompt tables that could exist.

Here we just add to new optional parameter that tells the web service to dynamically pull back labels and any descriptions.

  • "includeAllDescriptions": true
  • "includeAllFieldLabels": true
POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted..
Content-Type: application/json
Host: dev.ib.cedarhillsgroup.com
Content-Length: 151

{
  "rowLimit": 3,
  "includeAllDescriptions": true,
  "includeAllFieldLabels": true,
  "records": [
    {
      "recordName": "PSROLEDEFN"
    }
  ]
}

Here is the response.

You will see that there is the following pattern.

  • Field labels are output as {{fieldName}}_defaultLabel
  • Fields with XLATs or prompt tables are exported as {{fieldName}}_description

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 1075
content-type: application/json; encoding=UTF-8
date: Tue, 06 Jun 2023 05:29:49 GMT

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        {
          "rowNumber": 1,
          "ROLENAME": "ACM Administrator",
          "ROLENAME_defaultLabel": "Role Name",
          "VERSION": 1,
          "VERSION_defaultLabel": "Version",
          "ROLETYPE": "U",
          "ROLETYPE_defaultLabel": "Role Type",
          "ROLETYPE_description": "User List",
          "DESCR": "ACM Administrator",
          "DESCR_defaultLabel": "Description",
          "QRYNAME": "",
          "QRYNAME_defaultLabel": "Role-Query Name",
          "ROLESTATUS": "A",
          "ROLESTATUS_defaultLabel": "Role Status",
          "ROLESTATUS_description": "Active",
          "RECNAME": "",
          "RECNAME_defaultLabel": "Record (Table) Name",
          "FIELDNAME": "",
          "FIELDNAME_defaultLabel": "Field Name",
          "PC_EVENT_TYPE": "",
          "PC_EVENT_TYPE_defaultLabel": "PeopleCode Event Name",
          "QRYNAME_SEC": "",
          "QRYNAME_SEC_defaultLabel": "Query Name",
          "PC_FUNCTION_NAME": "",
          "PC_FUNCTION_NAME_defaultLabel": "PeopleCode Function Name",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_PCODE_RULE_ON_defaultLabel": "PeopleCode Rule Enabled",
          "ROLE_QUERY_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON_defaultLabel": "Query Rule Enabled",
          "LDAP_RULE_ON": "N",
          "LDAP_RULE_ON_defaultLabel": "Directory Rule Enabled",
          "DESCRLONG": "",
          "DESCRLONG_defaultLabel": "Description",
          "ALLOWNOTIFY": "Y",
          "ALLOWNOTIFY_defaultLabel": "Allow notification",
          "ALLOWLOOKUP": "Y",
          "ALLOWLOOKUP_defaultLabel": "Allow Recipient Lookup",
          "LASTUPDDTTM": "2020-02-19-13.49.14.223597",
          "LASTUPDDTTM_defaultLabel": "Last Update Date/Time",
          "LASTUPDOPRID": "PPLSOFT",
          "LASTUPDOPRID_defaultLabel": "Last Update User ID"
        },
        {
          "rowNumber": 2,
          "ROLENAME": "ADS Designer",
          "ROLENAME_defaultLabel": "Role Name",
          "VERSION": 1,
          "VERSION_defaultLabel": "Version",
          "ROLETYPE": "U",
          "ROLETYPE_defaultLabel": "Role Type",
          "ROLETYPE_description": "User List",
          "DESCR": "ADS Designer",
          "DESCR_defaultLabel": "Description",
          "QRYNAME": "",
          "QRYNAME_defaultLabel": "Role-Query Name",
          "ROLESTATUS": "A",
          "ROLESTATUS_defaultLabel": "Role Status",
          "ROLESTATUS_description": "Active",
          "RECNAME": "",
          "RECNAME_defaultLabel": "Record (Table) Name",
          "FIELDNAME": "",
          "FIELDNAME_defaultLabel": "Field Name",
          "PC_EVENT_TYPE": "",
          "PC_EVENT_TYPE_defaultLabel": "PeopleCode Event Name",
          "QRYNAME_SEC": "",
          "QRYNAME_SEC_defaultLabel": "Query Name",
          "PC_FUNCTION_NAME": "",
          "PC_FUNCTION_NAME_defaultLabel": "PeopleCode Function Name",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_PCODE_RULE_ON_defaultLabel": "PeopleCode Rule Enabled",
          "ROLE_QUERY_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON_defaultLabel": "Query Rule Enabled",
          "LDAP_RULE_ON": "N",
          "LDAP_RULE_ON_defaultLabel": "Directory Rule Enabled",
          "DESCRLONG": "Role for Application Dataset Designer",
          "DESCRLONG_defaultLabel": "Description",
          "ALLOWNOTIFY": "Y",
          "ALLOWNOTIFY_defaultLabel": "Allow notification",
          "ALLOWLOOKUP": "Y",
          "ALLOWLOOKUP_defaultLabel": "Allow Recipient Lookup",
          "LASTUPDDTTM": "2020-02-19-13.49.14.223642",
          "LASTUPDDTTM_defaultLabel": "Last Update Date/Time",
          "LASTUPDOPRID": "PPLSOFT",
          "LASTUPDOPRID_defaultLabel": "Last Update User ID"
        },
        {
          "rowNumber": 3,
          "ROLENAME": "AG Composer Administrator",
          "ROLENAME_defaultLabel": "Role Name",
          "VERSION": 1,
          "VERSION_defaultLabel": "Version",
          "ROLETYPE": "U",
          "ROLETYPE_defaultLabel": "Role Type",
          "ROLETYPE_description": "User List",
          "DESCR": "AG Composer Administrator",
          "DESCR_defaultLabel": "Description",
          "QRYNAME": "",
          "QRYNAME_defaultLabel": "Role-Query Name",
          "ROLESTATUS": "A",
          "ROLESTATUS_defaultLabel": "Role Status",
          "ROLESTATUS_description": "Active",
          "RECNAME": "",
          "RECNAME_defaultLabel": "Record (Table) Name",
          "FIELDNAME": "",
          "FIELDNAME_defaultLabel": "Field Name",
          "PC_EVENT_TYPE": "",
          "PC_EVENT_TYPE_defaultLabel": "PeopleCode Event Name",
          "QRYNAME_SEC": "",
          "QRYNAME_SEC_defaultLabel": "Query Name",
          "PC_FUNCTION_NAME": "",
          "PC_FUNCTION_NAME_defaultLabel": "PeopleCode Function Name",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_PCODE_RULE_ON_defaultLabel": "PeopleCode Rule Enabled",
          "ROLE_QUERY_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON_defaultLabel": "Query Rule Enabled",
          "LDAP_RULE_ON": "N",
          "LDAP_RULE_ON_defaultLabel": "Directory Rule Enabled",
          "DESCRLONG": "This role provides access to the setup pages to define, manage, and maintain Activity Guide Composer templates.",
          "DESCRLONG_defaultLabel": "Description",
          "ALLOWNOTIFY": "Y",
          "ALLOWNOTIFY_defaultLabel": "Allow notification",
          "ALLOWLOOKUP": "Y",
          "ALLOWLOOKUP_defaultLabel": "Allow Recipient Lookup",
          "LASTUPDDTTM": "2019-05-22-09.34.01.102262",
          "LASTUPDDTTM_defaultLabel": "Last Update Date/Time",
          "LASTUPDOPRID": "PPLSOFT",
          "LASTUPDOPRID_defaultLabel": "Last Update User ID"
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-05.29.49.000000",
    "psftTransactionId": "28121750-042b-11ee-9a28-e332b6feab8c",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 1,
  "apiVersion": "2023-03-14",
  "nextPageNumber": 2
}

Adding Some Criteria

In those examples above, we were just pulling back all the rows from the PSROLEDEFN table in a paginated way and only returning the top 3. Let’s limit our request to roles starting with “SA”% using the criteriaFields and remove some fields from that output that are not important to our integration partner using excludeFields. PsoftQL also has syntax for advanced SQL clauses but the criteriaFields is good for simple use cases. When you configure PsoftQL you can substitute user parameters from the HTTP request instead of hard coding like we are doing here for demo purposes.

We also removed the labels by removing includeAllFieldLabels from the request just to make the output more succinct.

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted..
Content-Type: application/json
Host: dev.ib.cedarhillsgroup.com
Content-Length: 394

{
  "rowLimit": 3,
  "includeAllDescriptions": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "excludeFields": ["RECNAME", "FIELDNAME", "PC_EVENT_TYPE", "QRYNAME_SEC", "VERSION", "QRYNAME", "ALLOWNOTIFY"],
      "criteriaFields": [
        {
            "fieldName": "ROLENAME",
            "fieldValue": "SA%",
            "operator": "LIKE"
        }
        ]
    }
  ]
}

Here is the response.

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 686
content-type: application/json; encoding=UTF-8
date: Tue, 06 Jun 2023 05:42:59 GMT

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        {
          "rowNumber": 1,
          "ROLENAME": "SA Administrator",
          "ROLETYPE": "U",
          "ROLETYPE_description": "User List",
          "DESCR": "SA and CR Administrator",
          "ROLESTATUS": "A",
          "ROLESTATUS_description": "Active",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "Student Administration and Contributor Relations administrator.  Do not modify.",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2004-05-11-21.49.57.000000",
          "LASTUPDOPRID": "PPLSOFT"
        },
        {
          "rowNumber": 2,
          "ROLENAME": "SAIP Administrator",
          "ROLETYPE": "U",
          "ROLETYPE_description": "User List",
          "DESCR": "SAIP Administrator",
          "ROLESTATUS": "A",
          "ROLESTATUS_description": "Active",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "Adminster Student Administration Integration Platform",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2007-07-11-16.45.39.000000",
          "LASTUPDOPRID": "PPLSOFT"
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-05.42.59.000000",
    "psftTransactionId": "feb52cc9-042c-11ee-9a28-e332b6feab8c",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Joining Tables - Automatically

Ok, we just hit 2nd gear. Let’s shift into 3rd gear and join in some child tables!

We want to join in a few more tables. They have the following hierarchy

  • PSROLEDEFN - Defines a role
    • PSROLECLASS - Defines Permission lists (CLASSID) assigned to a role.
      • PSCLASSDEFN - The permission list definition.
        • PSAUTHBUSCOMP - The component interface authorizations

We use the PsoftQL syntax to pass in more records but this time we tell it using the parentRecordName what the parent should be and SWS will automatically join the record based on the key fields. There are some more advanced options you can override the join logic if joining “strange” tables that are documented in detail in the syntax documentation. The default behavior works in 90% of the cases.

We will also just ask for one row because you are going to have to scroll.

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted..
Content-Type: application/json
Host: dev.ib.cedarhillsgroup.com
Content-Length: 717

{
  "rowLimit": 1,
  "includeAllDescriptions": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "excludeFields": [
        "RECNAME",
        "FIELDNAME",
        "PC_EVENT_TYPE",
        "QRYNAME_SEC",
        "VERSION",
        "QRYNAME",
        "ALLOWNOTIFY"
      ],
      "criteriaFields": [
        {
          "fieldName": "ROLENAME",
          "fieldValue": "SA%",
          "operator": "LIKE"
        }
      ]
    },
      {
      "recordName": "PSROLECLASS",
      "parentRecordName": "PSROLEDEFN"
    },
    {
      "recordName": "PSCLASSDEFN",
      "parentRecordName": "PSROLECLASS"
    },
    {
      "recordName": "PSAUTHBUSCOMP",
      "parentRecordName": "PSCLASSDEFN"
    }
  ]
}

Here is the response. In this case, this role only had one permission list and a bunch of Component Interface permissions (PSAUTHBUSCOMP). I deleted some of the rows so you did not have to scroll so much.

HTTP/1.1 200 OK
connection: close
content-encoding: gzip
content-length: 2731
content-type: application/json; encoding=UTF-8
date: Tue, 06 Jun 2023 05:56:54 GMT

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        {
          "rowNumber": 1,
          "ROLENAME": "SA Administrator",
          "ROLETYPE": "U",
          "ROLETYPE_description": "User List",
          "DESCR": "SA and CR Administrator",
          "ROLESTATUS": "A",
          "ROLESTATUS_description": "Active",
          "PC_FUNCTION_NAME": "",
          "ROLE_PCODE_RULE_ON": "N",
          "ROLE_QUERY_RULE_ON": "N",
          "LDAP_RULE_ON": "N",
          "DESCRLONG": "Student Administration and Contributor Relations administrator.  Do not modify.",
          "ALLOWLOOKUP": "Y",
          "LASTUPDDTTM": "2004-05-11-21.49.57.000000",
          "LASTUPDOPRID": "PPLSOFT",
          "PSROLECLASS": {
            "objectType": "record",
            "objectName": "PSROLECLASS",
            "fields": [
              {
                "ROLENAME": "SA Administrator",
                "CLASSID": "HCCPCSSA1000",
                "PSCLASSDEFN": {
                  "objectType": "record",
                  "objectName": "PSCLASSDEFN",
                  "fields": [
                    {
                      "CLASSID": "HCCPCSSA1000",
                      "VERSION": 131,
                      "CLASSDEFNDESC": "CS Administration - All Pages",
                      "TIMEOUTMINUTES": 0,
                      "DEFAULTBPM": "",
                      "STARTAPPSERVER": 0,
                      "ALLOWPSWDEMAIL": 0,
                      "LASTUPDDTTM": "2020-12-18-09.13.22.000000",
                      "LASTUPDOPRID": "PPLSOFT",
                      "PSAUTHBUSCOMP": {
                        "objectType": "record",
                        "objectName": "PSAUTHBUSCOMP",
                        "fields": [
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_LIST",
                            "BCNAME_description": "Enrollment List for the Term",
                            "BCMETHOD": "GetEnrlFldXlat",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_LIST",
                            "BCNAME_description": "Enrollment List for the Term",
                            "BCMETHOD": "GetSection",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_REQUEST",
                            "BCNAME_description": "Submit Enrollment Request",
                            "BCMETHOD": "Cancel",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_REQUEST",
                            "BCNAME_description": "Submit Enrollment Request",
                            "BCMETHOD": "Create",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_REQUEST",
                            "BCNAME_description": "Submit Enrollment Request",
                            "BCMETHOD": "Find",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "ENRL_REQUEST",
                            "BCNAME_description": "Submit Enrollment Request",
                            "BCMETHOD": "Get",
                            "AUTHORIZEDACTIONS": 4
                          },
                          {
                            "CLASSID": "HCCPCSSA1000",
                            "BCNAME": "SCC_CHESSN_AUS_CI",
                            "BCNAME_description": "Chessn Page CI",
                            "BCMETHOD": "Save",
                            "AUTHORIZEDACTIONS": 4
                          }
                        ]
                      }
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-05.56.54.000000",
    "psftTransactionId": "f075d5ba-042e-11ee-9a28-e332b6feab8c",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 1,
  "apiVersion": "2023-03-14",
  "nextPageNumber": 2
}

So what did we learn from the examples above? That SWS PsoftQL can do a lot of stuff for you automatically with a short syntax. We did not show you how effective dating and effective status is also handled automatically. However, those are better covered in some of the detailed examples for each PeopleSoft pillar in the later sections of this chapter.

PsoftQL Syntax

Here is the base JSON syntax example for one field. Most of these parameters are completely optional.


{
  "isDebugMode": false,
  "rowLimit": 1,
  "pageNumber": 3,
  "includeFieldTypes": false,
  "includeAllDescriptions": false,
  "includeKeyFieldIndicators": false,
  "includeAllFieldLabels": false,
  "noEffectiveDateLogic": false,
  "effectiveDateOverride": "1888-01-01",
  "noEffectiveStatusLogic": false,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "includeDescriptionsFor": [
        "ROLESTATUS",
        "ROLETYPE"
      ],
      "excludeFields": [
        "ALLOWNOTIFY",
        "VERSION"
      ],
      "parentRecordName": "",
      "useParentEffectiveDate": true,
      "doNotAutoJoinToParent": false,
      "criteriaFields": [
        {
          "fieldName": "ROLENAME",
          "fieldValue": "A%",
          "operator": "LIKE"
        }
      ]
    }
  ]
}

There is also an XML syntax that has the same parameter names but there are some subtle differences due to how XML and JSON are slightly different, especially around arrays. This is an XML example of a parent-child request.

<?xml version="1.0" encoding="UTF-8" ?>
<request>
    <records>
        <noEffectiveStatusLogic>true</noEffectiveStatusLogic>
        <noEffectiveDateLogic>true</noEffectiveDateLogic>
        <includeFieldTypes>true</includeFieldTypes>
        <includeAllDescriptions>false</includeAllDescriptions>
        <includeKeyFieldIndicators>false</includeKeyFieldIndicators>
        <includeAllFieldLabels>true</includeAllFieldLabels>
        <record>
            <recordName>PSOPRDEFN</recordName>
            <excludeFields fieldName="OPERPSWD" />
            <excludeFields fieldName="PTOPERPSWDV2" />
            <excludeFields fieldName="OPERPSWDSALT" />
            <excludeFields fieldName="ENCRYPTED" />
            <parentRecordName></parentRecordName>
            <includeDescriptionsFor fieldName="LANGUAGE_C" />
            <sqlWhereClause><![CDATA[]]></sqlWhereClause>
            <criteriaFields fieldName="OPRID" fieldValue="AJORDAN" operator="=" />
        </record>
        <record>
            <recordName>PSROLEUSER</recordName>
            <parentRecordName>PSOPRDEFN</parentRecordName>
            <includeDescriptionsFor fieldName="" />
            <excludeFields fieldName="" />
            <sqlWhereClause><![CDATA[]]></sqlWhereClause>
            <joinFields parentField="OPRID" childField="ROLEUSER" />
        </record>
    </records>
    <isDebugMode>true</isDebugMode>
    <rowLimit>30</rowLimit>
</request>

Important Notes

  • PsoftQL is case-sensitive.
  • JSON can be very picky. If you have not worked with JSON before, I would suggest you use online JSON Linters or if you are using VSCODE I would suggest finding a JSON linter plugin.
  • JSON requires some escaping of certain characters and this is especially true if you are passing complex where clauses in sqlWhereClause

PsoftQL Syntax Detail

  • rowLimit - (integer, optional, Override the default 50 row limit.)
  • includeFieldTypes - (boolean, optional, default: false) - Set to true if you want the PeopleSoft field types to come back. This will also output field length data. This should generally only be true during development as it causes extra processing at run time.
  • includeAllDescriptions - (boolean, optional, default: false) Set to true if you want to include all field descriptions to come back. This should generally only be true during development as it causes extra processing at run time.
  • pageNumber - (integer, optional) Request a different page number other than the first page
  • noEffectiveDateLogic - (boolean, default: false) This parameter is optional and only applies to record with EFFDT field. Set this to true if you want to include all historical rows. The default behavior when false is that only the most effective dated row is included.
  • noEffectiveStatusLogic - (boolean, default: false) This parameter is optional and only applies to records with EFF_STATUS. Set this to true if you want to include rows where EFF_STATUS = 'I'.
  • effectiveDateOverride - (string, optional) This parameter is optional and only applies to record with EFFDT field. Optional date in YYYY-MM-DD format to override the current date logic used for effective dated records.
  • includeAllFieldLabels (boolean, default: false) Optional boolean to ask that the default field label be included in the output in the form of {fieldName}_label. This will be the default field label at the field level. There are many times in PeopleSoft where the page field label is actually different. So this label will NOT always be the correct label that shows up on user-facing pages. This should generally only be true during development as it causes extra processing at run time.
  • isDebugMode - (boolean, optional, default: false)
  • records (array of objects, at least one object required) This is a required list of records you want from the database. There has to be at least one value with the parentRecordName blank to designate the ‘root parent record’. There can only be one record listed with parentRecordName blank. “,
    • recordName (string, required) The record name that you want to return data from. It must be whitelisted to you.
    • parentRecordName (string, optional) There must be one array with this blank. There can only be one record with this blank. Then any child table you are requesting must have this specified with the recordName you want to be the parent.”
    • useParentEffectiveDate (boolean, optional for child records only, default: false) If set to true this forces the parent effective date to be used instead of letting the logic build an effective dated clause. This should be set to true for any true children records where a PeopleSoft component enforces the child effective date to match the parent. Only a PeopleSoft expert will know this by looking at the data entry component.
    • doNotAutoJoinToParent (boolean, default false) - Only in rare situations do you want to set this to true. If set to true then the code will not look at common key fields between parent and child and automatically join them. The request MUST include an additional key-value pair array to tell the API how to join the parent and child. These are cases where the parent and child do not share a common field or key values.
    • joinFields (optional array of objects) - This allows you to tell the web service to join on fields between parent and child that are not obvious or where parent and child do NOT share a common field name but are truly related. OPRID = ROLEUSER, SETID = DEPTID_SETID, EMPLID = MANAGER_ID, etc. This is generally NOT required for this web service. If you are joining standard PeopleSoft tables they generally have the same field and the keys match. However, there are times when you need to force a join between fields. In that case, you can use theses. You might also need to set doNotAutoJoinToParent = true if you want complete control over how the table are joined.
      • parentField - The name of the parent field that you want to join to.
      • childField - The name of the child field you want to join to.
    • sqlWhereClause (string, optional) Optional where clause to limit the data. Do NOT include ‘where’ at the front. The record is loaded with an alias of ‘A’.
    • criteriaFields (array, optional) This is another way to pass in structured data that is used to build a where clause to limit the data. See Operator Examples for worked examples of every operator and LIKE wildcard pattern.
      • fieldName (string) Field name to limit. It must exist on the record.
      • fieldValue (string) The value to limit
      • operator (string) SQL Operator to use like equal to, greater than, etc
    • excludeFields (array of string, optional) Optional array of fields that you do not want to include in the output.
    • includeDescriptionsFor (array of string, optional) Optional array of fields that want to include the description for any codes.
    • includeKeyFieldIndicators (boolean, default: false) Optional boolean to ask for a return of fields that are defined as keys on the record. The response will be {fieldName}_isKey: true only if the field is defined as a key. If the field is not defined as a key then this attribute will NOT be present. This should generally only be true during development as it causes extra processing at run time.
    • orderByFields (array of objects, optional) Optional array of objects to specify custom ordering of results. When specified, this overrides the default key-based ordering used for pagination. Each object has the following properties:
      • fieldName (string, required) The field name to order by. Must exist on the record.
      • sortOrder (string, optional) The sort direction - either ASC (ascending) or DESC (descending). Defaults to ASC if not specified.

Date fields

PeopleSoft was developed to be database-independent. There are some built-in “META-SQL” functions that can be leveraged in the request to handle data parsing.

  • %datein - can be used to get the current system date. At run time this will expand to either the Oracle or SQL Server (or other).
  • %currentdatein can be used to get the current system date time.

There are several other “meta-sql” variables around data math that can be helpful.

Pagination

This web service handles pagination of large amounts of data. This web service does pagination using a dynamic “Order by” clause that is generated at run-time. The “root” record is used and drives the sort order. By default, the web service looks at the root record and uses the defined database keys to automatically generate an “order by” clause based on the keys and the field ordering of the keys on the record definition. These attributes are all accessible as meta-data inside PeopleSoft.

Custom Ordering With orderByFields

You can override the default key-based ordering by specifying the orderByFields parameter on a record. This allows you to control the sort order of your results, which is especially useful when you want to see the most recently updated records first or sort by a specific business field.

{
  "rowLimit": 10,
  "records": [
    {
      "recordName": "PSPROJECTDEFN",
      "sqlWhereClause": "LASTUPDDTTM is not null",
      "orderByFields": [
        {
          "fieldName": "LASTUPDDTTM",
          "sortOrder": "DESC"
        }
      ]
    }
  ]
}

You can specify multiple fields to create a compound sort order:

{
  "rowLimit": 10,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "orderByFields": [
        {
          "fieldName": "ROLESTATUS",
          "sortOrder": "ASC"
        },
        {
          "fieldName": "LASTUPDDTTM",
          "sortOrder": "DESC"
        }
      ]
    }
  ]
}

Note: When using orderByFields, pagination will use your custom ordering instead of the default key-based ordering.

Next, we will look at a simple pagination example. First, we will ask for pageNumber 1 and ask for only 2 rows to be returned per page using the rowLimit parameter.

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 2,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL"
    }
  ]
}

The response coming back will look like this:

{
  "data": {
    "ACAD_ORG_TBL": {
      "objectType": "record",
      "objectName": "ACAD_ORG_TBL",
      "fields": [
        {
          "rowNumber": 1,
          "ACAD_ORG": "A&S",
          "EFFDT": "1900-01-01",
          "EFF_STATUS": "A",
          "DESCR": "Faculty of Arts and Social Sci",
          "DESCRSHORT": "Arts&SoSci",
          "DESCRFORMAL": "Faculty of Arts and Social Sciences",
          "INSTITUTION": "PSGBR",
          "CAMPUS": "MAIN",
          "MANAGER_ID": "",
          "INSTR_EDIT": "P",
          "CAMPUS_EDIT": "N",
          "SUBJECT_EDIT": "N",
          "COURSE_EDIT": "N"
        },
        {
          "rowNumber": 2,
          "ACAD_ORG": "ACAD DIV",
          "EFFDT": "1900-01-01",
          "EFF_STATUS": "A",
          "DESCR": "Academic Division",
          "DESCRSHORT": "Acad Div",
          "DESCRFORMAL": "Academic Division",
          "INSTITUTION": "PSCCS",
          "CAMPUS": "",
          "MANAGER_ID": "",
          "INSTR_EDIT": "P",
          "CAMPUS_EDIT": "N",
          "SUBJECT_EDIT": "N",
          "COURSE_EDIT": "N"
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-06.29.55.000000",
    "psftTransactionId": "8d83aaed-0433-11ee-96c1-2302b6923a7a",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 1,
  "apiVersion": "2023-03-14",
  "nextPageNumber": 2
}

At the bottom you will see a nextPageNumber with a value of “2”. That tells you that there are more pages to pull. To pull the next page you would request the following. The only thing that is changing is the pageNumber parameter.

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 2,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL"
    }
  ]
}

How do you know when you have hit the last page? The response will NOT have a nextPageNumber property. You can also ask for pages beyond the result set like this:

Request:

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 2,
  "pageNumber": 999,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL"
    }
  ]
}

The response will look similar to this:

{
  "data": {
    "ACAD_ORG_TBL": {
      "objectType": "record",
      "objectName": "ACAD_ORG_TBL",
      "fields": []
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": {
    "toolsVer": "8.58.07",
    "currentUser": "CHG_SWS_UNIT_TESTER",
    "responseDTTM": "2023-06-06-06.30.58.000000",
    "psftTransactionId": "b28e2298-0433-11ee-96c1-2302b6923a7a",
    "dbname": "CS92U020",
    "dbType": "ORACLE",
    "serverTimeZone": "PST",
    "serverDirectory": "C:\\Users\\psoft\\psft\\pt\\8.58\\appserv\\APPDOM",
    "debugMessages": ""
  },
  "pageNumber": 9999,
  "apiVersion": "2023-03-14"
}

Aggregate Queries

PsoftQL supports aggregate queries that allow you to group data and apply aggregate functions like COUNT, SUM, AVG, MIN, and MAX. This is useful for reporting and analytics scenarios where you need summarized data rather than individual rows.

Aggregate Mode

To enable aggregate mode, set isAggregate: true at the top level of your request. When aggregate mode is enabled:

  • Pagination is NOT supported - The pageNumber parameter must be omitted or set to 1
  • Child records are NOT supported - Only a single root record is allowed (no parentRecordName)
  • aggregateConfig is required - You must specify which fields to group by and which aggregate functions to apply

Aggregate Syntax

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "SOME_TABLE",
      "aggregateConfig": {
        "groupByFields": ["FIELD1", "FIELD2"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "total_count"
          },
          {
            "function": "SUM(AMOUNT)",
            "outputLabel": "total_amount"
          }
        ]
      }
    }
  ]
}

Aggregate Configuration

  • isAggregate - (boolean, required for aggregate queries) Set to true at the top level to enable aggregate mode.
  • aggregateConfig - (object, required on each record when isAggregate is true) Configuration for the aggregate query. This is specified at the record level to support future join capabilities.
    • groupByFields - (array of strings, optional) Fields to group results by. If empty or omitted, aggregates are computed across all matching rows.
    • aggregateFields - (array of objects, required) At least one aggregate function definition is required.
      • function - (string, required) The SQL aggregate function with the field or expression embedded (e.g., COUNT(*), COUNT(1), SUM(AMOUNT)).
      • outputLabel - (string, optional) Custom label for the output column. If not provided, a default label will be generated.

Supported Aggregate Functions

Function ExampleDescription
COUNT(*)Count all rows
COUNT(1)Count all rows (alternative syntax)
COUNT(FIELDNAME)Count non-null values in a field
COUNT(DISTINCT FIELDNAME)Count unique values in a field
SUM(FIELDNAME)Sum numeric values (must be a numeric field)
AVG(FIELDNAME)Average numeric values (must be a numeric field)
MIN(FIELDNAME)Minimum value (works with numeric, date, and string fields)
MAX(FIELDNAME)Maximum value (works with numeric, date, and string fields)

Aggregate Example 1: Count by Role Type

Count the number of roles grouped by role type:

{
  "isAggregate": true,
  "includeAllDescriptions": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "aggregateConfig": {
        "groupByFields": ["ROLETYPE"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "role_count"
          }
        ]
      }
    }
  ]
}

Response:

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "aggregateResult",
      "objectName": "PSROLEDEFN",
      "groupByFields": ["ROLETYPE"],
      "aggregateFields": ["role_count"],
      "results": [
        {
          "ROLETYPE": "U",
          "ROLETYPE_description": "User List",
          "role_count": 245
        },
        {
          "ROLETYPE": "Q",
          "ROLETYPE_description": "Query",
          "role_count": 12
        },
        {
          "ROLETYPE": "P",
          "ROLETYPE_description": "PeopleCode",
          "role_count": 8
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "isAggregate": true,
  "totalGroups": 3,
  "apiVersion": "2024-01-15"
}

Aggregate Example 2: Multiple Aggregates with Criteria

Calculate salary statistics by department for active employees:

{
  "isAggregate": true,
  "rowLimit": 50,
  "records": [
    {
      "recordName": "JOB",
      "criteriaFields": [
        {
          "fieldName": "EMPL_STATUS",
          "fieldValue": "A",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": ["DEPTID"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "employee_count"
          },
          {
            "function": "SUM(ANNUAL_RT)",
            "outputLabel": "total_salary"
          },
          {
            "function": "AVG(ANNUAL_RT)",
            "outputLabel": "avg_salary"
          },
          {
            "function": "MIN(ANNUAL_RT)",
            "outputLabel": "min_salary"
          },
          {
            "function": "MAX(ANNUAL_RT)",
            "outputLabel": "max_salary"
          }
        ]
      }
    }
  ]
}

Aggregate Example 3: Total Count Without Grouping

Get a simple count of all active roles (no grouping):

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "criteriaFields": [
        {
          "fieldName": "ROLESTATUS",
          "fieldValue": "A",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": [],
        "aggregateFields": [
          {
            "function": "COUNT(*)",
            "outputLabel": "total_active_roles"
          }
        ]
      }
    }
  ]
}

Response:

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "aggregateResult",
      "objectName": "PSROLEDEFN",
      "groupByFields": [],
      "aggregateFields": ["total_active_roles"],
      "results": [
        {
          "total_active_roles": 265
        }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "isAggregate": true,
  "totalGroups": 1,
  "apiVersion": "2024-01-15"
}

Aggregate Response Structure

Aggregate responses differ from standard responses:

AspectStandard QueryAggregate Query
objectType"record""aggregateResult"
Data array"fields""results"
Row identifierrowNumber includedNot included
PaginationpageNumber, nextPageNumberNot present
Top-level indicatorNone"isAggregate": true
CountNone"totalGroups" count

Features That Work in Aggregate Mode

  • rowLimit - Limits the number of grouped result rows returned (useful for “top N” queries)
  • criteriaFields / sqlWhereClause - Filters rows before aggregation (equivalent to SQL WHERE clause)
  • noEffectiveDateLogic, effectiveDateOverride - Affects which rows are included in aggregation
  • includeAllDescriptions - Adds descriptions for GROUP BY field values (XLAT/prompt table translations)

Features Not Supported in Aggregate Mode

  • pageNumber > 1 - Pagination is not supported; returns an error
  • Multiple records with parentRecordName - Child records are not supported; returns an error
  • includeFieldTypes, includeAllFieldLabels, includeKeyFieldIndicators - These are ignored in aggregate mode

4.3 - Campus Solutions PsoftQL - Examples

PsoftQL Examples for PeopleSoft Campus Solutions (CS)

Here are some PsoftQL examples for PeopleSoft Campus Solutions. If you have some others you’ve created, please feel free to contact us and let me know, and we can update this document.

You can use these as a starting point.

Academic Setup

Institution Table

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "INSTITUTION_TBL"
    }
  ]
}

Academic Organization Table

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL"
    }
  ]
}

Academic Organization Table

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL"
    }
  ]
}

Academic Group Table

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_GROUP_TBL"
    }
  ]
}

Subject table

This is a setup table in PeopleSoft that defines the list of subjects.

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 200,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "SUBJECT_TBL"
    }
  ]
}

Instruction Mode

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 20,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "INSTRUCT_MODE"
    }
  ]
}

Component Xlat

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 20,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "PSXLATITEM",
      "sqlWhereClause": " FIELDNAME = 'SSR_COMPONENT'"
    }
  ]
}

PeopleSoft Terms

{
    "isDebugMode": false,
    "rowLimit": 9999,
    "pageNumber": 1,
    "noEffectiveDateLogic": false,
    "noEffectiveStatusLogic": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": true,
    "records": [
        {
            "recordName": "TERM_TBL",
            "sqlWhereClause": "INSTITUTION = '{{institution}}'",
            "criteriaFields": [
                {
                    "fieldName": "TERM_BEGIN_DT",
                    "fieldValue": "2020-01-01",
                    "operator": ">="
                }
            ]
        },
        {
            "recordName": "SESSION_TBL",
            "parentRecordName": "TERM_TBL"
        }
    ]
}

Campus Table

This is a setup table in PeopleSoft that defines the list of campuses.

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 20,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "CAMPUS_TBL"
    }
  ]
}

Acad Career Table

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "ACAD_CAR_TBL"
    }
  ]
}

Meeting Pattern Setup

{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 30,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "STND_MTGPAT_TBL"
    }
  ]
}

Course Catalog

This will return all active courses. It will be paginated as we are limiting it to 10 rows per page (rowLimit)

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "noEffectiveDateLogic": false,
    "noEffectiveStatusLogic": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": true,
    "records": [
        {
            "recordName": "CRSE_CATALOG",
            "sqlWhereClause": ""
        },
        {
            "recordName": "CRSE_OFFER",
            "parentRecordName": "CRSE_CATALOG",
            "useParentEffectiveDate": true
        },
        {
            "recordName": "CRSE_ATTRIBUTES",
            "parentRecordName": "CRSE_CATALOG",
            "useParentEffectiveDate": true
        },
        {
            "recordName": "CRSE_COMPONENT",
            "parentRecordName": "CRSE_CATALOG",
            "useParentEffectiveDate": true
        },
        {
            "recordName": "CRSE_ATTENDANCE",
            "parentRecordName": "CRSE_COMPONENT",
            "useParentEffectiveDate": true
        },
        {
            "recordName": "CMPNT_CHRSTC",
            "parentRecordName": "CRSE_COMPONENT",
            "useParentEffectiveDate": true
        }
    ]
}

Course Catalog Change Detection (Incremental Sync)

Integration partners that mirror the PeopleSoft course catalog rarely want the whole catalog on every run. The SCC_ROW_UPD_DTTM column records the per-row last-update timestamp; compare it against the timestamp of the previous successful run to pull only records that changed since then.

{
  "isDebugMode": false,
  "rowLimit": 5,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "CRSE_CATALOG",
      "criteriaFields": [
        { "fieldName": "SCC_ROW_UPD_DTTM", "fieldValue": "1900-01-01", "operator": ">=" }
      ]
    }
  ]
}

Substitute 1900-01-01 for the timestamp of the previous successful run (in YYYY-MM-DD or YYYY-MM-DD-HH.MM.SS.NNNNNN format). On the first run, a low sentinel date returns everything; on subsequent runs, only rows whose SCC_ROW_UPD_DTTM has advanced past the previous watermark.

Grading Basis Setup


{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "rowLimit": 20,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "GRADESCHEME_TBL"
    },
     {
      "recordName": "GRADE_BASIS_TBL",
      "parentRecordName":"GRADESCHEME_TBL"
    }
    ,
     {
      "recordName": "GRD_BASE_CHOICE",
      "parentRecordName":"GRADE_BASIS_TBL"
    }
    ,
     {
      "recordName": "GRADE_TBL",
      "parentRecordName":"GRADE_BASIS_TBL"
    }
  ]
}

Facilities

This is really from the “bottom” up and we are bringing in related tables that are defined independently.

{
  "isDebugMode": true,
  "includeFieldTypes": false,
  "rowLimit": 1,
  "pageNumber": 1,
  "records": [
    {
      "recordName": "FACILITY_TBL"
    },
     {
      "recordName": "BLDG_TBL",
      "parentRecordName":"FACILITY_TBL",
      "sqlWhereClause": ""
    }
    ,
     {
      "recordName": "LOCATION_TBL",
      "parentRecordName":"FACILITY_TBL"
    }
    ,
     {
      "recordName": "FACILITY_CHRSTC",
      "parentRecordName":"FACILITY_TBL",
      "useParentEffectiveDate": true
    }
    ,
     {
      "recordName": "FACIL_BLACK_OUT",
      "parentRecordName":"FACILITY_TBL",
      "useParentEffectiveDate": true
    }
    ,
     {
      "recordName": "CAMPUS_LOC_TBL",
      "parentRecordName":"FACILITY_TBL",
            "useParentEffectiveDate": true
    }
  ]
}

PeopleSoft Classes

This will pull in a list of classes and we assume that you have two “path” or query string parameters to substitute in the {{institution}} and {{strm}}

{

 "isDebugMode": false,
    "rowLimit": 3,
    "pageNumber": 1,
    "noEffectiveDateLogic": false,
    "noEffectiveStatusLogic": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": true,
    "records": [
        {
            "recordName": "CLASS_TBL",
            "sqlWhereClause": " INSTITUTION = {{institution}} and STRM = {{strm}}"
        },
        {
            "recordName": "CLASS_ATTRIBUTE",
            "parentRecordName": "CLASS_TBL"
        },     
        {
            "recordName": "CLASS_MTG_PAT",
            "parentRecordName": "CLASS_TBL"
        },
        {
            "recordName": "CLASS_INSTR",
            "parentRecordName": "CLASS_MTG_PAT"
        },
        {
            "recordName": "CLASS_NOTES",
            "parentRecordName": "CLASS_TBL"
        },
        {
            "recordName": "CLASS_NOTES_TBL",
            "parentRecordName": "CLASS_NOTES"
        },
         {
            "recordName": "FACILITY_TBL",
            "parentRecordName": "CLASS_MTG_PAT"
        }
    ]
}

Student Data

Get Person Information

{
    "isDebugMode": "{{debugMode}}",
    "rowLimit": 100,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PERSON",
            "sqlWhereClause": "  EMPLID = {{emplid}}"
        },
        {
            "recordName": "NAMES",
            "parentRecordName": "PERSON"
        },
        {
            "recordName": "EMAIL_ADDRESSES",
            "parentRecordName": "PERSON"
        },
        {
            "recordName": "PERSONAL_PHONE",
            "parentRecordName": "PERSON"
        },
        {
            "recordName": "PERS_DATA_EFFDT",
            "parentRecordName": "PERSON"
        },
        {
            "recordName": "ADDRESSES",
            "parentRecordName": "PERSON"
        },
        {
            "recordName": "PERS_NID",
            "parentRecordName": "PERSON"
        }
    ]
}

Student Program and Plan Setup

When joining effective-dated child records to effective-dated parents, set useParentEffectiveDate: true on the child. This keeps the child’s EFFDT logic aligned with the parent row SWS selected, so you don’t end up with a child EFFDT newer or older than its parent. Without this flag the child record would run its own effective-date computation against SYSDATE, which can diverge from the parent.

{
    "isDebugMode": "{{debugMode}}",
    "rowLimit": 100,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "ACAD_PROG",
            "sqlWhereClause": "  EMPLID = {{emplid}}  "
        },
        {
            "recordName": "ACAD_PLAN",
            "parentRecordName": "ACAD_PROG",
            "useParentEffectiveDate": true
        }
    ]
}

More Complete Student Program and Plan Setup

Ported from a real Modern Campus integration, this walks the full student-career hierarchy: STDNT_CAREER (careers the student is pursuing) β†’ ACAD_PROG (each program under that career) β†’ ACAD_PLAN (majors/minors under each program), plus STDNT_CAR_TERM for per-term standing. Note useParentEffectiveDate: true on ACAD_PLAN so its EFFDT aligns with the ACAD_PROG row above it.

{
    "isDebugMode": "{{debugMode}}",
    "rowLimit": 100,
    "pageNumber": 1,
    "records": [

        {
            "recordName": "STDNT_CAREER",
            "sqlWhereClause": "  EMPLID = {{emplid}}"
        }
        ,
        {
            "recordName": "ACAD_PROG",
            "parentRecordName": "STDNT_CAREER"
        },
        {
            "recordName": "ACAD_PLAN",
            "parentRecordName": "ACAD_PROG",
            "useParentEffectiveDate": true
        }
        ,
        {
            "recordName": "STDNT_CAR_TERM",
            "parentRecordName": "STDNT_CAREER"
        }
    ]
}

Get A Students Service Indicators

{
    "isDebugMode": "{{debugMode}}",
    "includeFieldTypes": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
    {
        "recordName": "SRVC_IND_DATA",
        "sqlWhereClause": "EMPLID={{emplid}} and (SCC_SI_END_DT is null or SCC_SI_END_DT > %currentdatein) and (SCC_SI_END_TERM = '' OR NOT EXISTS ( SELECT 1 FROM PS_TERM_TBL B WHERE A.SCC_SI_END_TERM = B.STRM AND B.TERM_BEGIN_DT < %CURRENTDATEIN))",
        "includeDescriptionsFor": [
        "SRVC_IND_CD"
        ]
    },
    {
        "recordName": "SRVC_IND_CD_TBL",
        "parentRecordName": "SRVC_IND_DATA"
    },
    {
        "recordName": "SERVICE_IMPACT",
        "parentRecordName": "SRVC_IND_CD_TBL"
    },
    {
        "recordName": "SRVC_IN_RSN_TBL",
        "parentRecordName": "SRVC_IND_DATA"
    }
    ]
}

Get a Student’s Schedule for a Term

The student-schedule join is a textbook case for explicit joinFields. STDNT_ENRL and CLASS_TBL share three key fields (CLASS_NBR, STRM, INSTITUTION), and the default auto-join would attempt to match on every common key, which produces the wrong cardinality. Setting doNotAutoJoinToParent: true disables the default behavior so SWS uses only the joinFields you supply.


{
    "isDebugMode": "{{debugMode}}",
    "includeFieldTypes": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
    {
        "recordName": "STDNT_ENRL",
        "sqlWhereClause": "EMPLID= {{emplid}} AND STRM = {{strm}} AND STDNT_ENRL_STATUS = 'E'"
    },
    {
        "recordName": "CLASS_TBL",
        "parentRecordName": "STDNT_ENRL",
        "doNotAutoJoinToParent": true,
        "joinFields": [
        {
            "parentField": "CLASS_NBR",
            "childField": "CLASS_NBR"
        },
        {
            "parentField": "STRM",
            "childField": "STRM"
        },
        {
            "parentField": "INSTITUTION",
            "childField": "INSTITUTION"
        }
        ]
    },
    {
        "recordName": "CLASS_MTG_PAT",
        "parentRecordName": "CLASS_TBL"
    }
    ,
    {
        "recordName": "CAMPUS_MTG",
        "parentRecordName": "CLASS_TBL"
    }

]
}

4.4 - PsoftQL Operators & Filtering Examples

Examples showing every criteriaFields operator PsoftQL supports, LIKE wildcard patterns, and combined filtering with sqlWhereClause

PsoftQL’s criteriaFields clause lets you filter rows with any of the operators SQL supports for scalar comparisons. The examples below demonstrate each operator, the two wildcards supported by LIKE, how multiple criteria combine with implicit AND, and how criteriaFields can coexist with a free-form sqlWhereClause.

All examples here are drawn verbatim from the integration test suite in doc-internal/test/test-pql-criteria-operators.http. Each test is numbered to match the source file.

Supported Operators

OperatorMeaningNotes
=EqualDefault operator if omitted
<Less thanString or numeric comparison, depends on field type
>Greater than
<=Less than or equal
>=Greater than or equal
<>Not equalEquivalent to !=
!=Not equal (alternate syntax)Equivalent to <>
LIKEPattern match% matches any substring, _ matches exactly one character
INMembershipSupplied via fieldValues array (see syntax reference for details)

Multiple entries in the criteriaFields array are joined with AND. For OR logic, move the condition into sqlWhereClause.


Test 1: Greater Than (>) β€” PSROLEDEFN by last-update timestamp

Use > when you want rows strictly after a threshold. A common use case is fetching roles that have been edited since your last sync.

Request

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": false,
    "includeKeyFieldIndicators": false,
    "rowLimit": 5,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSROLEDEFN",
            "criteriaFields": [
                {
                    "fieldName": "LASTUPDDTTM",
                    "fieldValue": "2020-01-01",
                    "operator": ">"
                }
            ]
        }
    ]
}

Response

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Every returned row has LASTUPDDTTM > '2020-01-01'.


Test 2: Less Than (<) β€” PSXLATITEM by field name

Use < for rows strictly before a threshold. This example also shows how noEffectiveDateLogic and noEffectiveStatusLogic flags disable the default SWS filters β€” useful when you want raw rows from tables that would otherwise be filtered to the current effective row or active status.

Request

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": false,
    "includeKeyFieldIndicators": false,
    "noEffectiveDateLogic": true,
    "noEffectiveStatusLogic": true,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSXLATITEM",
            "criteriaFields": [
                {
                    "fieldName": "FIELDNAME",
                    "fieldValue": "D",
                    "operator": "<"
                }
            ]
        }
    ]
}

Response

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "PSXLATITEM": {
      "objectType": "record",
      "objectName": "PSXLATITEM",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Tests 3 & 4: Greater-or-Equal (>=) and Less-or-Equal (<=) β€” CRSE_CATALOG range

Range filters use the inclusive comparison operators. The pair below walks the Course Catalog: first everything from CRSE_ID = '000100' onward, then everything up to and including CRSE_ID = '000010'.

Request β€” Test 3: >=

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": false,
    "includeKeyFieldIndicators": false,
    "rowLimit": 5,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "CRSE_CATALOG",
            "criteriaFields": [
                {
                    "fieldName": "CRSE_ID",
                    "fieldValue": "000100",
                    "operator": ">="
                }
            ]
        }
    ]
}

Request β€” Test 4: <=

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": false,
    "includeKeyFieldIndicators": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "CRSE_CATALOG",
            "criteriaFields": [
                {
                    "fieldName": "CRSE_ID",
                    "fieldValue": "000010",
                    "operator": "<="
                }
            ]
        }
    ]
}

Response

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "CRSE_CATALOG": {
      "objectType": "record",
      "objectName": "CRSE_CATALOG",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Tests 5 & 6: Not-Equal (<> and !=) β€” PSROLEDEFN by role status

PsoftQL accepts both <> and != for inequality. They generate identical SQL β€” pick whichever reads more naturally for your team.

Request β€” Test 5: <>

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSROLEDEFN",
            "criteriaFields": [
                {
                    "fieldName": "ROLESTATUS",
                    "fieldValue": "A",
                    "operator": "<>"
                }
            ]
        }
    ]
}

Request β€” Test 6: !=

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSROLEDEFN",
            "criteriaFields": [
                {
                    "fieldName": "ROLESTATUS",
                    "fieldValue": "I",
                    "operator": "!="
                }
            ]
        }
    ]
}

Response

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Test 8: Multiple Criteria (AND logic) β€” PSOPRDEFN

When you supply more than one entry in criteriaFields, PsoftQL joins them with AND. The example below finds users whose OPRID begins with A and whose account is locked, while using excludeFields to strip sensitive password columns from the response.

Request

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": true,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSOPRDEFN",
            "excludeFields": ["OPERPSWD", "PTOPERPSWDV2", "OPERPSWDSALT", "ENCRYPTED"],
            "criteriaFields": [
                {
                    "fieldName": "OPRID",
                    "fieldValue": "A%",
                    "operator": "LIKE"
                },
                {
                    "fieldName": "ACCTLOCK",
                    "fieldValue": "1",
                    "operator": "="
                }
            ]
        }
    ]
}

Response

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "PSOPRDEFN": {
      "objectType": "record",
      "objectName": "PSOPRDEFN",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Tests 9, 10, 11: LIKE Wildcard Patterns

PsoftQL passes LIKE patterns through to SQL, so the standard wildcards apply:

  • % matches any sequence of zero or more characters
  • _ matches exactly one character

The three examples below demonstrate each placement pattern: trailing, surrounding, and single-character.

Test 9: Leading wildcard (%ADMIN) β€” roles ending in ADMIN

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSROLEDEFN",
            "criteriaFields": [
                {
                    "fieldName": "ROLENAME",
                    "fieldValue": "%ADMIN",
                    "operator": "LIKE"
                }
            ]
        }
    ]
}

Test 10: Wildcards on both sides (%USER%) β€” roles containing USER

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSROLEDEFN",
            "criteriaFields": [
                {
                    "fieldName": "ROLENAME",
                    "fieldValue": "%USER%",
                    "operator": "LIKE"
                }
            ]
        }
    ]
}

Test 11: Single-character wildcard (_lient) β€” 6-letter XLATs ending in “lient”

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "noEffectiveDateLogic": true,
    "noEffectiveStatusLogic": true,
    "rowLimit": 20,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "PSXLATITEM",
            "criteriaFields": [
                {
                    "fieldName": "XLATLONGNAME",
                    "fieldValue": "_lient",
                    "operator": "LIKE"
                }
            ]
        }
    ]
}

Response (common shape)

HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8

{
  "data": {
    "PSROLEDEFN": {
      "objectType": "record",
      "objectName": "PSROLEDEFN",
      "fields": [
        { "...": "response-body-to-be-captured-from-live-endpoint" }
      ]
    }
  },
  "responseCode": 200,
  "errorMessages": "",
  "meta": { "...": "..." },
  "pageNumber": 1,
  "apiVersion": "2023-03-14"
}

Test 12: Combining criteriaFields with sqlWhereClause β€” TERM_TBL

You can mix structured criteria with a free-form SQL clause. They are joined with AND. Use sqlWhereClause for expressions that don’t fit the scalar comparison model β€” subqueries, functions, OR logic β€” and keep criteriaFields for straightforward column-value checks so the intent is obvious.

Request

POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json

{
    "isDebugMode": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
        {
            "recordName": "TERM_TBL",
            "sqlWhereClause": "ACAD_CAREER = 'CNED'",
            "criteriaFields": [
                {
                    "fieldName": "INSTITUTION",
                    "fieldValue": "PSUNV",
                    "operator": "="
                }
            ]
        }
    ]
}

Where to go next

4.5 - PeopleTools PsoftQL - Examples

PsoftQL Examples for PeopleSoft PeopleTools Tables

In this section we will show detailed PsoftQL examples of how to extract PeopleTools information using SWS PsoftQL Syntax.

Users and Their Roles

This example shows how to pull PSOPRDEFN and PSROLEUSER information and join them together to get a list of users and their roles. We also exclude some fields that are not needed. Additionally, we include the descriptions for the LANGUAGE_CD and CURRENCY_CD fields.

{
    "isDebugMode": false,
    "includeFieldTypes": false,
    "includeAllDescriptions": false,
    "includeKeyFieldIndicators": false,
    "includeAllFieldLabels": false,
    "rowLimit": 10,
    "pageNumber": 1,
    "records": [
    {
        "recordName": "PSOPRDEFN",
        "includeDescriptionsFor": ["LANGUAGE_CD", "CURRENCY_CD"],
        "excludeFields": ["VERSION", "EXPENT", "OPERPSWD", "PTOPERPSWDV2", "OPERPSWDSALT", "ENCRYPTED"],
        "criteriaFields": [

        ]
    },
    {
        "recordName": "PSROLEUSER",
        "parentRecordName": "PSOPRDEFN",
        "doNotAutoJoinToParent": true,
        "joinFields": [
            {"parentField": "OPRID", "childField": "ROLEUSER"}
        ],
        "excludeFields": ["ROLEUSER"]
    }

    ]
}

PeopleSoft Record Definitions

Export PeopleSoft Record Definitions and their fields. This example shows how to pull PSRECDEFN, PSRECFIELDDB, and PSDBFIELD information and join them together to get a list of record definitions and their fields.


{
  "isDebugMode": false,
  "includeFieldTypes": false,
  "includeAllDescriptions": true,
  "includeKeyFieldIndicators": false,
  "includeAllFieldLabels": false,
  "rowLimit": 2,
  "records": [
    {
      "recordName": "PSRECDEFN",
      "includeDescriptionsFor": []
    },
    {
      "recordName": "PSRECFIELDDB",
      "parentRecordName": "PSRECDEFN"
    },
    {
      "recordName": "PSDBFIELD",
      "parentRecordName": "PSRECFIELDDB"
    }
  ]
}

Recently Modified Projects

This example shows how to use orderByFields to get the most recently modified App Designer projects. By ordering by LASTUPDDTTM in descending order, you get the most recently updated projects first.

{
  "isDebugMode": false,
  "rowLimit": 10,
  "records": [
    {
      "recordName": "PSPROJECTDEFN",
      "sqlWhereClause": "LASTUPDDTTM is not null",
      "orderByFields": [
        {
          "fieldName": "LASTUPDDTTM",
          "sortOrder": "DESC"
        }
      ]
    }
  ]
}

Recently Modified Roles

Get the most recently modified PeopleSoft roles, sorted by last update date descending. This is useful for auditing recent security changes.

{
  "isDebugMode": false,
  "rowLimit": 20,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "excludeFields": ["RECNAME", "FIELDNAME", "PC_EVENT_TYPE", "QRYNAME_SEC", "VERSION", "QRYNAME"],
      "orderByFields": [
        {
          "fieldName": "LASTUPDDTTM",
          "sortOrder": "DESC"
        }
      ]
    }
  ]
}

Roles Sorted by Status and Last Update

This example demonstrates compound sorting with multiple orderByFields. Roles are sorted first by status (Active roles first alphabetically), then by last update date with most recent first.

{
  "isDebugMode": false,
  "rowLimit": 50,
  "includeAllDescriptions": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "excludeFields": ["RECNAME", "FIELDNAME", "PC_EVENT_TYPE", "QRYNAME_SEC", "VERSION", "QRYNAME"],
      "orderByFields": [
        {
          "fieldName": "ROLESTATUS",
          "sortOrder": "ASC"
        },
        {
          "fieldName": "LASTUPDDTTM",
          "sortOrder": "DESC"
        }
      ]
    }
  ]
}

4.6 - Aggregate SQL Translation Examples

Internal reference showing how PsoftQL aggregate requests translate to SQL, with real response bodies for the most-used patterns

This document shows how PsoftQL aggregate requests translate to SQL queries. It is both an internal reference for implementation and a guide for callers assembling aggregate requests. Examples 4, 5, 6, and 9 include sample response bodies drawn from the integration test suite in doc-internal/test/test-pql-aggregate.http.

Example 1: Simple COUNT with GROUP BY

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "aggregateConfig": {
        "groupByFields": ["ROLETYPE"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "role_count"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.ROLETYPE,
    COUNT(1) AS role_count
FROM PS_PSROLEDEFN A
GROUP BY A.ROLETYPE
ORDER BY A.ROLETYPE

Example 2: COUNT with Criteria (WHERE clause)

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "criteriaFields": [
        {
          "fieldName": "ROLESTATUS",
          "fieldValue": "A",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": ["ROLETYPE"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "active_role_count"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.ROLETYPE,
    COUNT(1) AS active_role_count
FROM PS_PSROLEDEFN A
WHERE A.ROLESTATUS = 'A'
GROUP BY A.ROLETYPE
ORDER BY A.ROLETYPE

Example 3: Multiple GROUP BY Fields

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "aggregateConfig": {
        "groupByFields": ["ROLETYPE", "ROLESTATUS"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "role_count"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.ROLETYPE,
    A.ROLESTATUS,
    COUNT(1) AS role_count
FROM PS_PSROLEDEFN A
GROUP BY A.ROLETYPE, A.ROLESTATUS
ORDER BY A.ROLETYPE, A.ROLESTATUS

Example 4: Multiple Aggregate Functions

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "JOB",
      "criteriaFields": [
        {
          "fieldName": "EMPL_STATUS",
          "fieldValue": "A",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": ["DEPTID"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "employee_count"
          },
          {
            "function": "SUM(ANNUAL_RT)",
            "outputLabel": "total_salary"
          },
          {
            "function": "AVG(ANNUAL_RT)",
            "outputLabel": "avg_salary"
          },
          {
            "function": "MIN(ANNUAL_RT)",
            "outputLabel": "min_salary"
          },
          {
            "function": "MAX(ANNUAL_RT)",
            "outputLabel": "max_salary"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.DEPTID,
    COUNT(1) AS employee_count,
    SUM(A.ANNUAL_RT) AS total_salary,
    AVG(A.ANNUAL_RT) AS avg_salary,
    MIN(A.ANNUAL_RT) AS min_salary,
    MAX(A.ANNUAL_RT) AS max_salary
FROM PS_JOB A
WHERE A.EMPL_STATUS = 'A'
GROUP BY A.DEPTID
ORDER BY A.DEPTID

Example 5: No GROUP BY (Total Aggregation)

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "aggregateConfig": {
        "groupByFields": [],
        "aggregateFields": [
          {
            "function": "COUNT(*)",
            "outputLabel": "total_roles"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    COUNT(*) AS total_roles
FROM PS_PSROLEDEFN A

Note: No GROUP BY clause, no ORDER BY clause when there are no groupByFields.

Example 6: COUNT DISTINCT

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "STDNT_ENRL",
      "criteriaFields": [
        {
          "fieldName": "STDNT_ENRL_STATUS",
          "fieldValue": "E",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": ["INSTITUTION", "STRM"],
        "aggregateFields": [
          {
            "function": "COUNT(DISTINCT EMPLID)",
            "outputLabel": "unique_students"
          },
          {
            "function": "COUNT(1)",
            "outputLabel": "total_enrollments"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.INSTITUTION,
    A.STRM,
    COUNT(DISTINCT A.EMPLID) AS unique_students,
    COUNT(1) AS total_enrollments
FROM PS_STDNT_ENRL A
WHERE A.STDNT_ENRL_STATUS = 'E'
GROUP BY A.INSTITUTION, A.STRM
ORDER BY A.INSTITUTION, A.STRM

Example 7: With Effective Date Logic

When noEffectiveDateLogic is false (default) and the record has EFFDT:

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "ACAD_ORG_TBL",
      "aggregateConfig": {
        "groupByFields": ["INSTITUTION"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "org_count"
          }
        ]
      }
    }
  ]
}

Generated SQL (Oracle)

SELECT
    A.INSTITUTION,
    COUNT(1) AS org_count
FROM PS_ACAD_ORG_TBL A
WHERE A.EFFDT = (
    SELECT MAX(A2.EFFDT)
    FROM PS_ACAD_ORG_TBL A2
    WHERE A2.ACAD_ORG = A.ACAD_ORG
    AND A2.EFFDT <= SYSDATE
)
GROUP BY A.INSTITUTION
ORDER BY A.INSTITUTION

Example 8: With rowLimit (Top N)

PsoftQL Request

{
  "isAggregate": true,
  "rowLimit": 10,
  "records": [
    {
      "recordName": "JOB",
      "criteriaFields": [
        {
          "fieldName": "EMPL_STATUS",
          "fieldValue": "A",
          "operator": "="
        }
      ],
      "aggregateConfig": {
        "groupByFields": ["DEPTID"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "employee_count"
          }
        ]
      }
    }
  ]
}

Generated SQL (Oracle 12c+)

SELECT
    A.DEPTID,
    COUNT(1) AS employee_count
FROM PS_JOB A
WHERE A.EMPL_STATUS = 'A'
GROUP BY A.DEPTID
ORDER BY A.DEPTID
FETCH FIRST 10 ROWS ONLY

Generated SQL (SQL Server)

SELECT TOP 10
    A.DEPTID,
    COUNT(1) AS employee_count
FROM PS_JOB A
WHERE A.EMPL_STATUS = 'A'
GROUP BY A.DEPTID
ORDER BY A.DEPTID

Example 9: With sqlWhereClause

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEDEFN",
      "sqlWhereClause": "A.ROLENAME LIKE 'SA%' AND A.ROLESTATUS = 'A'",
      "aggregateConfig": {
        "groupByFields": ["ROLETYPE"],
        "aggregateFields": [
          {
            "function": "COUNT(1)",
            "outputLabel": "role_count"
          },
          {
            "function": "MAX(LASTUPDDTTM)",
            "outputLabel": "last_updated"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.ROLETYPE,
    COUNT(1) AS role_count,
    MAX(A.LASTUPDDTTM) AS last_updated
FROM PS_PSROLEDEFN A
WHERE A.ROLENAME LIKE 'SA%' AND A.ROLESTATUS = 'A'
GROUP BY A.ROLETYPE
ORDER BY A.ROLETYPE

PsoftQL Request

{
  "isAggregate": true,
  "records": [
    {
      "recordName": "PSROLEUSER",
      "aggregateConfig": {
        "groupByFields": ["ROLENAME"],
        "aggregateFields": [
          {
            "function": "COUNT(DISTINCT ROLEUSER)",
            "outputLabel": "user_count"
          }
        ]
      }
    }
  ]
}

Generated SQL

SELECT
    A.ROLENAME,
    COUNT(DISTINCT A.ROLEUSER) AS user_count
FROM PS_PSROLEUSER A
GROUP BY A.ROLENAME
ORDER BY A.ROLENAME

Implementation Notes

SQL Generation Pattern

SELECT
    {groupByFields with A. prefix},
    {aggregateFields with A. prefix on field references}
FROM PS_{recordName} A
WHERE {criteriaFields converted to SQL}
    {AND sqlWhereClause if provided}
    {AND effective date logic if applicable}
GROUP BY {groupByFields with A. prefix}
ORDER BY {groupByFields with A. prefix}
{FETCH FIRST rowLimit ROWS ONLY if rowLimit provided}

Field Prefix Handling

The aggregate function string needs to be parsed to add the A. alias prefix to field names:

InputOutput
COUNT(*)COUNT(*) (no change)
COUNT(1)COUNT(1) (no change)
COUNT(EMPLID)COUNT(A.EMPLID)
COUNT(DISTINCT EMPLID)COUNT(DISTINCT A.EMPLID)
SUM(ANNUAL_RT)SUM(A.ANNUAL_RT)
AVG(SALARY)AVG(A.SALARY)
MIN(HIRE_DT)MIN(A.HIRE_DT)
MAX(LASTUPDDTTM)MAX(A.LASTUPDDTTM)

Validation Checks

  1. If isAggregate: true and pageNumber > 1 β†’ Error
  2. If isAggregate: true and multiple records with parentRecordName β†’ Error
  3. If aggregateConfig missing on the record when isAggregate: true β†’ Error
  4. If aggregateFields empty when isAggregate: true β†’ Error
  5. Validate field names in groupByFields exist on the record
  6. Parse and validate field names in function expressions exist on the record (except * and literals like 1)

4.7 - PsoftQL Validator

Validate your PsoftQL JSON or XML syntax against the official schema

Paste your PsoftQL JSON or XML below to validate it against the official schema. The format is auto-detected and validation runs automatically as you type.

1
Paste PsoftQL JSON or XML above to validate.

5 - Road Map

SWS Product Road Map

Planned Features

The following features are planned for future SWS releases.

OpenAPI / Swagger Generation

Automatically generate OpenAPI (Swagger) specifications from your SWS configurations. This will allow integration partners to auto-generate client SDKs and explore your APIs through standard tooling.

PsoftQL Enhancements

  • Data translation in PsoftQL syntax
  • Improved PsoftQL validation in the UI
  • Record aliasing in PsoftQL
  • Field aliasing in PsoftQL

Platform Improvements

  • Improved HTTP status codes under certain error conditions
  • Request and response logging

6 - Project Meta

Meta Information about the SWS product

6.1 - Why was SWS Created?

Some backstory on why and how SWS was created.

Innovation is often born out of frustration with the status quo. This was certainly the case for Chris Malek, who had been working on numerous integration projects that required the same web services to be re-implemented time and time again. Each one with some similar features and others with unique features. The process of deploying these services became increasingly difficult for even minor changes, particularly when pulling data from PeopleSoft, with the same tables being used repeatedly.

After deploying bespoke web services that were not generic enough, it became clear that a new more scalable solution was needed. This is where SWS comes in.

SWS was created to address the challenges of repetitive and unsustainable web services. It takes advantage of Chris Malek’s expertise with PeopleTools and web services, leveraging PeopleSoft metadata to create a single bolt-on web service that can handle up to 95 percent of use cases for extracting data from PeopleSoft.

Rather than re-implementing the same web services over and over again, SWS offers a one-stop-shop solution that can handle everything from small tables to large tables to nested data.

This innovative solution has been a game-changer for those in need of web services that are both efficient and sustainable. It has eliminated the need for constant re-implementation and made the deployment process much more streamlined. Development time of data extraction went from weeks to minutes!

Cedar Hills Group, Inc. has several clients and system integrators using SWS in various releases. This public release is the culmination of all 20 years of PeopleSoft integration experience and packaged in the best-of-breed solution.

6.2 - Terminology

This section highlights some terminology used in this SWS documentation

See our Integration Book - Terminology Sections

HTTP
HTTP (Hypertext Transfer Protocol) is a standardized protocol used for the transfer of information on the World Wide Web. It establishes a connection between a client and a server, allowing for the request and delivery of web resources such as HTML pages, images, videos, and more. HTTP operates on a request-response model, where the client sends a request to the server, and the server responds with the requested data or an appropriate error message. It forms the foundation for web browsing, enabling the retrieval and display of web content in a user-friendly manner.
Client
An HTTP user of your API. This could be a simple command line (CLI) application like curl or wget. It will likely be some external application that needs to pull data from PeopleSoft.
PSIGW
PeopleSoft Integration Gateway - This is the web server that is running your integration broker.
Encoding
The way the data is “wrapped” and presented to the client: JSON, XML or CSV.
Request
The HTTP request made by the client. This includes the URL path, query strings, and HTTP Headers.
Response
The data that comes back from the server to the client in reply to an HTTP Request.
REST
REST (Representational State Transfer) is an architectural style for designing networked applications, particularly web services, that emphasizes a stateless, client-server communication model. It promotes the use of standard HTTP methods like GET, POST, PUT, and DELETE to perform operations on resources identified by unique URLs (Uniform Resource Locators). RESTful systems leverage the principles of scalability, simplicity, and modifiability, allowing different clients to interact with the server using a uniform and predictable interface. It facilitates the exchange of data in various formats, such as JSON or XML, and supports the separation of concerns between client and server components.
PsoftQL
A proprietary JSON and XML syntax to request data from SWS. Read more on the PsoftQL Syntax

6.3 - SWS Assumptions and Limitations

This article discussions some of the assumptions and limitations of the product.

Assumptions

  • You have a working integration broker setup.
  • You want to pull information from PeopleSoft.
  • PeopleTools version greater than 8.56

Limitations

  • SWS supports only extracting information out of PeopleSoft where PeopleSoft is acting as the HTTP Server.
  • As a general rule, data is exported in the PeopleSoft format with the PeopleSoft field names and field formats.
    • A future release may lift this limitation.
  • SWS does not yet handle data translation or transformation.
    • A future release may lift this limitation.

7 - Legal & Licensing

Licensing terms, the annual self-audit procedure, and copyright notices for SWS (Software Web Services) from Cedar Hills Group, Inc.

Licensing Resources

SWS is a perpetually licensed PeopleSoft module. Because SWS is delivered as source-level PeopleCode that runs entirely inside your PeopleSoft environment, licensing compliance depends on a written agreement between you and Cedar Hills Group, a straightforward annual self-audit, and clear copyright notices. The pages below describe each in detail.

License Agreement

The end user license agreement (EULA) that governs your use of SWS: scope, restrictions, audit rights, confidentiality of source, termination, and governing law.

Read the License Agreement β†’

Self-Audit Procedure

How to run the annual SWS licensing self-audit. A single SQL query against your PeopleSoft database returns the web service count for each environment. No connection to Cedar Hills Group is required β€” you run it, you send the result.

Read the Audit Procedure β†’

Copyright Notice

The copyright notice embedded in every SWS file, the customer’s obligation to preserve it, and a plain-language summary of what copyright protection means for both parties.

Read the Copyright Notice β†’

  • Purchase SWS β€” License tiers and pricing.
  • SWS Installation β€” Lists the delivered objects, including the C_SWS_CONF_TBL record that the self-audit query targets.

7.1 - SWS License Agreement

1. Parties

This End User License Agreement (the “Agreement”) is between Cedar Hills Group, Inc., a United States corporation (“Licensor”, “Cedar Hills Group”, or “CHG”), and the legal entity identified on the executed purchase order (“Customer” or “Licensee”). The effective date of this Agreement is the date on the executed purchase order (the “Effective Date”).

2. Definitions

  • “SWS Software” means the Cedar Hills Group Software Web Services module, delivered as a PeopleSoft Application Designer project containing PeopleCode, records, fields, pages, components, and related configuration objects, and any patches, upgrades, or releases provided by Licensor under this Agreement.
  • “PeopleSoft Environment” means a distinct PeopleSoft database instance (a single DBNAME) operated by Customer, including Development, Test, and Production environments.
  • “Production Instance” means a PeopleSoft Environment used by Customer to serve live, business-of-record transactions to end users or downstream systems.
  • “Web Service” means a single active row in the SWS configuration record C_SWS_CONF_TBL whose Active flag is set to Yes. Inactive rows are not counted for licensing purposes. The definition aligns with the self-audit procedure.
  • “License Tier” means the Junior, Pro, Enterprise, or System Integrator tier described on the Purchase SWS page, as identified on Customer’s executed purchase order.
  • “Documentation” means the documentation published at https://sws.books.cedarhillsgroup.com/, including this page.
  • “Support Hours” means the hours included in Customer’s License Tier for the then-current annual support and maintenance term.

3. Grant of License

Subject to Customer’s compliance with this Agreement and timely payment of the applicable fees, Cedar Hills Group grants Customer a perpetual, non-exclusive, non-transferable, non-sublicensable license to install, operate, and use the SWS Software within the number of PeopleSoft Environments and up to the Web Service count authorized by Customer’s License Tier. The current tier definitions, counts, and prices are published on the Purchase SWS page and are incorporated by reference into this Agreement.

The license is perpetual with respect to the version of the SWS Software delivered under Customer’s then-current support and maintenance term. If Customer elects not to renew annual support, Customer retains the right to continue operating the last-delivered version within the scope of this Agreement, but is not entitled to further patches, upgrades, or support.

4. Restrictions

Customer shall not, and shall not permit any third party to:

  1. Redistribute, sell, rent, lease, sublicense, or otherwise transfer the SWS Software or any portion of it to any third party.
  2. Install or operate the SWS Software in any PeopleSoft Environment that is not owned or operated by Customer, or on behalf of any entity other than Customer, except as expressly permitted for the System Integrator tier under a separately executed agreement.
  3. Remove, alter, or obscure the copyright notices, file headers, or attribution embedded in the SWS Software. See the Copyright Notice page for the required header text.
  4. Use the SWS Software to develop, market, or operate a product that competes with the SWS Software.
  5. Exceed the Web Service count authorized by Customer’s License Tier without truing up to the appropriate tier under Section 6.
  6. Transfer this Agreement, or any rights under it, to a successor entity (including by merger, acquisition, or change of control) without the prior written consent of Cedar Hills Group, which shall not be unreasonably withheld.

5. Source Code and Confidentiality

Customer acknowledges that the PeopleSoft platform requires the SWS Software to be delivered in source-readable form (PeopleCode, records, pages, and related Application Designer objects). The source form of the SWS Software is and shall remain the confidential information and trade secret of Cedar Hills Group.

Customer shall:

  1. Protect the SWS Software source with the same degree of care Customer uses to protect its own confidential information of similar sensitivity, and in no event with less than reasonable care.
  2. Restrict access to the SWS Software to employees, contractors, and authorized consultants who have a need to know for the purposes permitted under this Agreement and who are bound by written confidentiality obligations substantially equivalent to those in this Section.
  3. Not publish, post, share, or otherwise disclose the SWS Software source or any material portion of it to any public or third-party repository, message board, social media platform, AI training data set, or distribution service.

The confidentiality obligations in this Section survive termination of this Agreement.

6. Audit Rights and True-Up

Cedar Hills Group may, no more than twice per calendar year, request that Customer run the standard SWS self-audit procedure published at the Self-Audit Procedure page and return the results to Cedar Hills Group within 30 days of the request. The self-audit is executed entirely by Customer inside Customer’s environment; Cedar Hills Group does not connect to Customer’s systems.

If the self-audit results show that Customer’s Web Service count exceeds the count authorized by Customer’s License Tier, Customer shall true up to the appropriate License Tier within 30 days of the audit result. True-up fees are calculated at the then-current tier pricing published on the Purchase SWS page, prorated for the remainder of the current support term.

Customer’s refusal to run the self-audit, or failure to respond within 30 days, is a material breach of this Agreement under Section 11.

All right, title, and interest in and to the SWS Software, including all intellectual property rights, remain with Cedar Hills Group. Customer receives no ownership interest under this Agreement, only the license expressly granted in Section 3. All rights not expressly granted are reserved by Cedar Hills Group.

The copyright notice required in every SWS Software file is described on the Copyright Notice page. Customer shall preserve those notices in every copy of the SWS Software in Customer’s environments.

8. Support and Maintenance

Support and maintenance is separately renewable and is described on the Purchase SWS page. The first year of support is included in the initial purchase price. Renewal is annual.

The perpetual license granted in Section 3 is not affected by Customer’s decision to renew or decline annual support. If Customer declines renewal, Customer continues to operate the last-delivered version under the terms of this Agreement, without further patches, upgrades, or Support Hours.

9. Warranty

Cedar Hills Group warrants that, for 90 days from the Effective Date, the SWS Software will perform substantially in accordance with the Documentation. Customer’s exclusive remedy, and Cedar Hills Group’s sole obligation, for breach of this warranty is, at Cedar Hills Group’s option, to (a) repair or replace the SWS Software, or (b) refund the license fees paid for the SWS Software and terminate this Agreement.

EXCEPT FOR THE LIMITED WARRANTY IN THIS SECTION, THE SWS SOFTWARE IS PROVIDED “AS IS” AND CEDAR HILLS GROUP DISCLAIMS ALL OTHER WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.

10. Limitation of Liability

IN NO EVENT SHALL CEDAR HILLS GROUP BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR FOR LOST PROFITS, LOST REVENUE, LOST DATA, OR BUSINESS INTERRUPTION, ARISING OUT OF OR RELATED TO THIS AGREEMENT OR THE SWS SOFTWARE, REGARDLESS OF THE THEORY OF LIABILITY.

Cedar Hills Group’s total aggregate liability under this Agreement shall not exceed the total fees paid by Customer to Cedar Hills Group in the twelve months preceding the event giving rise to the claim.

11. Termination

This Agreement, and the license granted under it, terminates automatically on any material breach by Customer that remains uncured 30 days after written notice from Cedar Hills Group. Material breach includes, without limitation:

  1. Unauthorized redistribution or disclosure of the SWS Software.
  2. Refusal to run, or failure to return the results of, the self-audit procedure under Section 6.
  3. Removal or obscuring of copyright notices required under Section 7.
  4. Non-payment of undisputed fees when due.

On termination, Customer shall:

  1. Immediately cease all use of the SWS Software.
  2. Within 30 days of termination, remove the SWS Software from every PeopleSoft Environment in Customer’s control, and certify in writing to Cedar Hills Group that removal is complete.

12. Governing Law and Venue

This Agreement is governed by the laws of the State of California, without regard to its conflict-of-laws rules. The state and federal courts located in California have exclusive jurisdiction over any dispute arising out of or related to this Agreement.

13. Entire Agreement; Amendments; Assignment; Severability

This Agreement, together with the executed purchase order and the pages of the SWS Documentation expressly incorporated by reference, is the entire agreement between the parties with respect to the SWS Software. It supersedes all prior and contemporaneous communications, whether written or oral.

This Agreement may be amended only by a written instrument signed by an authorized representative of each party.

Customer may not assign this Agreement, in whole or in part, without the prior written consent of Cedar Hills Group.

If any provision of this Agreement is held unenforceable, the remaining provisions shall remain in full force and effect, and the unenforceable provision shall be reformed to the minimum extent necessary to be enforceable.


7.2 - SWS Self-Audit Procedure

How to run the annual SWS license self-audit. A single SQL query against C_SWS_CONF_TBL returns the active web service count for each PeopleSoft environment.

Purpose

SWS licensing is tiered by the number of active web services configured in your PeopleSoft environment. To keep licensing simple and respect the reality that most PeopleSoft environments are operated inside regulated or air-gapped networks, Cedar Hills Group does not connect to your systems or install phone-home telemetry. Instead, compliance is verified with a customer-run self-audit: once a year (or within 30 days of a request from Cedar Hills Group), you run a short SQL query and send the result.

This page documents the query, how to submit the result, and the true-up process if your active web service count has grown past your License Tier.

What Counts as a Web Service

A “web service” for licensing purposes is a single active row in the SWS configuration record C_SWS_CONF_TBL. Inactive configurations do not count.

The C_SWS_CONF_TBL record is documented on the SWS Installation page as one of the delivered SWS objects. The Active flag and the URL path (CHG_DE_PATH) are described on the SWS Configuration page.

The Self-Audit Query

Run the query below once per PeopleSoft environment that has SWS installed (including Development, Test, and Production). Each environment is a separate line item for licensing.

-- SWS License Self-Audit β€” count of active web services in this environment.
-- Run this as a PeopleSoft DBA or equivalent, once per environment, and
-- return the result to Cedar Hills Group.
SELECT
    COUNT(*) AS ACTIVE_WEB_SERVICES
FROM PS_C_SWS_CONF_TBL
WHERE ACTIVE = 'Y';

For a more detailed report that lists every active web service path β€” useful for verifying the count and identifying stray or obsolete configurations before you report β€” run:

-- SWS License Self-Audit β€” detailed listing of every active web service.
SELECT
    CHG_DE_PATH AS URL_PATH,
    ACTIVE
FROM PS_C_SWS_CONF_TBL
WHERE ACTIVE = 'Y'
ORDER BY CHG_DE_PATH;

How to Submit Your Result

  1. Run the count query in every PeopleSoft environment that has SWS installed.
  2. Record the DBNAME of the environment and the active web service count. For environments with more than the count alone would justify, also run the detailed listing query and export it to CSV.
  3. Email the results to Cedar Hills Group at compliance@cedarhillsgroup.com. Include:
    • Your organization name and the License Tier on your current purchase order.
    • For each PeopleSoft environment: the DBNAME, a short label (Dev, Test, Prod), and the active web service count.
    • Optionally, the detailed listing CSV for your production environment.

CSV export is the most convenient submission format β€” most PeopleSoft DBAs already have SQL tools that export query results to CSV in two clicks.

Frequency

Run the self-audit:

  • Annually on the anniversary of your License Tier effective date, or
  • Within 30 days of a written request from Cedar Hills Group (which under the License Agreement may be made no more than once per calendar year).

If You Are Over Your Tier

If your active web service count has grown past your License Tier cap, the situation is treated as a true-up, not a penalty. The goal is correct licensing, not a gotcha.

  1. Contact Cedar Hills Group. The contact details on the Purchase SWS page apply.
  2. Cedar Hills Group will issue a purchase order for the next-higher tier, prorated for the remainder of your current support term.
  3. Once the true-up purchase order is paid, your License Tier is upgraded retroactive to the date the overage began.
  4. You continue operating SWS without interruption during the process.

There is no penalty for over-reporting. If you report 31 active web services on a Pro tier (cap of 30), the resolution is to move you to Enterprise at the prorated delta. Reporting accurately, even when you are slightly over, is the intended use of this procedure.

Frequently Asked Questions

Do I need to report inactive configurations? No. Only rows where the Active flag is set to Yes count. Inactive rows may be left in place for historical reference without affecting your license count.

What if one web service is defined in Test and Prod β€” is that one or two? Each PeopleSoft environment is audited separately. A single web service configured in both Test and Prod counts as one active web service in each environment. The License Tier caps apply per environment, not globally across all environments.

What if Cedar Hills Group never asks for an audit? The License Agreement requires you to run the self-audit annually on your license anniversary regardless of whether Cedar Hills Group requests it. If your result is within your tier, a short confirmation email is sufficient.

What happens to my audit result after I send it? Cedar Hills Group treats audit submissions as confidential business information. Results are used only to confirm licensing compliance and to issue true-up purchase orders when required. See the confidentiality terms in the License Agreement.

Can I automate the audit? Yes. The count query is safe to schedule as a PeopleSoft Query or a database job and have the result emailed to your SWS license owner. Some customers schedule the query quarterly to spot trends before the annual audit.

7.3 - SWS Copyright Notice

Copyright notice embedded in every SWS file, the customer’s obligation to preserve it, and a short explanation of why it matters to both parties.

Copyright Β© Cedar Hills Group, Inc. All rights reserved.

SWS (Software Web Services) is the property of Cedar Hills Group, Inc. and is protected by United States and international copyright law. Unauthorized reproduction, redistribution, or disclosure of any portion of SWS is prohibited.

The Required File Header

Every file delivered as part of SWS β€” PeopleCode, records, fields, pages, components, and supporting scripts β€” carries a header comment substantially in the following form:

/*
 * SWS (Software Web Services)
 * Copyright (c) Cedar Hills Group, Inc. All rights reserved.
 *
 * This file is licensed to the end customer under the SWS License Agreement.
 * See: https://sws.books.cedarhillsgroup.com/legal/license-agreement/
 *
 * Redistribution, public disclosure, or removal of this notice is prohibited.
 */

The exact wording may vary slightly between object types β€” PeopleCode comments use /* ... */, while some Application Designer objects record the notice in their Description or Long Description fields β€” but the substance is the same: this file belongs to Cedar Hills Group, and its use is governed by the SWS License Agreement.

Customer’s Obligation to Preserve the Notice

Under Section 4 of the License Agreement, the customer agrees not to remove, alter, or obscure any copyright notice or file header delivered with SWS. This obligation applies to:

  1. The SWS source files as installed in every PeopleSoft environment.
  2. Any copy of the SWS source files moved into version control, backup, archive, or migration tooling.
  3. Any excerpt of the SWS source shared with internal staff, contractors, or consultants authorized to access SWS under the License Agreement.

The customer may add its own comments β€” local implementation notes, change history, internal ticket references β€” but must not remove or modify the Cedar Hills Group header.

Removal of the header is listed in the License Agreement as a material breach and is a ground for termination of the license.

Copyright protects original creative expression automatically from the moment it is fixed in a tangible form. For SWS, that means every PeopleCode file, every Application Designer object, and every line of logic that ships to the customer is protected under United States and international copyright law the moment Cedar Hills Group writes it. No registration is required for protection to attach (though Cedar Hills Group may register copyrights with the U.S. Copyright Office as a matter of course).

Practically, copyright gives Cedar Hills Group the legal right to act if SWS is copied, redistributed, published, or disclosed outside the scope of the License Agreement β€” including seeking injunctive relief, damages, and, where applicable, statutory damages and attorney fees.

For the customer, that protection is useful for two reasons:

  1. It is what makes the license enforceable. The license is valuable to the customer precisely because the thing being licensed is protected property. A product that anyone could freely copy would not be sustainable for Cedar Hills Group to support long term.
  2. It clarifies responsibilities within the customer’s organization. The notice on every file makes it clear to every engineer, contractor, and consultant that the code is third-party licensed material β€” not the customer’s own code β€” and that the usual “copy this into the team snippets folder” reflex is not appropriate.

8 - About the Project

About Cedar Hills Group

SWS was created by Cedar Hills Group, Inc, a PeopleSoft consulting firm with over 25 years of integration expertise. Cedar Hills Group specializes in PeopleSoft integration strategy, web services, and data extraction solutions.

Getting Started

Ready to Purchase

See the Purchase Page for pricing tiers and licensing options.

About the Author

SWS was created by Chris Malek, who has spent 25+ years working with PeopleSoft integrations. Chris is also the author of Integration Broker – The Missing Manual, the most comprehensive guide to PeopleSoft Integration Broker.

9 - Purchase SWS

Choose Your SWS License

SWS is licensed per PeopleSoft environment with annual support and maintenance. Every tier includes JSON, XML, and CSV support, sample data, and bug fixes.

How the License Works

  • Perpetual license. You buy SWS once. You keep the right to run the version you licensed, even if you choose not to renew annual support.
  • Annual support and maintenance covers bug fixes, patches, all version upgrades (minor and major), your tier’s support hours, and access to the private GitHub repository. The first year is included in the purchase price; renewal is annual.
  • If you do not renew: SWS continues to run on the version you have. Renewal is required to resume patches, upgrades, and support.
  • Support hours do not roll over. Unused hours reset at each renewal.
Junior
$9,000
$9,000 first year Β· $1,800/year after

  • 10 web services
  • 1 production instance
  • 3 support hours per year
  • Self-service installation
  • Priority support available at $160/hour (same-business-day response)

Best for institutions getting started with a focused set of integrations.

Pro
$18,000
$18,000 first year Β· $3,600/year after

  • 30 web services
  • 1 production instance
  • 9 support hours per year
  • Self-service installation
  • Priority support available at $160/hour (same-business-day response)

Best for institutions with active integration programs across multiple vendors.

Enterprise
$27,000
$27,000 first year Β· $5,400/year after

  • Unlimited web services
  • Unlimited production instances
  • 18 support hours per year
  • 2 hours guided installation via Zoom
  • Priority support included

Best for institutions with broad integration needs and digital transformation initiatives.

System Integrator
Contact Us

Designed for consulting firms and system integrators serving multiple PeopleSoft institutions. Flexible licensing across your client portfolio, with support hours and installation assistance scaled to your engagement model.

Contact Chris Malek to discuss pricing for your practice.

What Every License Includes

SWS Platform

  • SWS GET Services for configuration-driven web services
  • PsoftQL advanced query language
  • Automatic PeopleSoft metadata handling (Effective Date, Status, Sequence, XLAT, prompt tables)
  • JSON, XML, and CSV output formats
  • Parent-child nested data structures
  • Built-in pagination
  • Response caching

Works Everywhere

  • Campus Solutions
  • Human Capital Management (HCM)
  • Financials
  • Compatible with PeopleTools 8.57+
  • Standard Application Designer project installation
  • Zero modifications to delivered PeopleSoft objects

Ongoing Support

  • Annual support hours with Cedar Hills Group
  • Bug fixes and patches while annual support is current
  • All version upgrades included (minor and major)
  • Sample data and configuration examples
  • Access to private GitHub repository for discussions and issue tracking
  • Documentation and knowledge base access

The ROI Case

A single traditional PeopleSoft web service costs $3,600-$13,000 to develop using conventional Integration Broker methods. That means:

  • The Junior tier pays for itself after just 1-3 integrations
  • The Pro tier replaces $108,000-$390,000 worth of custom development (30 web services)
  • The Enterprise tier provides unlimited integrations for less than the cost of developing 3 traditional web services

The annual recurring fee covers ongoing support, bug fixes, and access to the Cedar Hills Group team. It is a fraction of the cost of maintaining custom-built integrations.

5-Year Total Cost Example (Pro Tier)

  • Year 1: $18,000 (purchase, includes first year of support)
  • Years 2–5: $3,600/year Γ— 4 = $14,400
  • 5-year total: $32,400 β€” for 30 web services, all upgrades included, ongoing support from the Cedar Hills Group team.

Compared to the typical cost of building and maintaining 30 bespoke Integration Broker web services, this is a fraction of the investment.

Frequently Asked Questions

What does the annual support and maintenance fee include? Bug fixes, patches, all version upgrades (minor and major), your tier’s support hours, and access to the private GitHub repository. The first year is bundled into the purchase price. If you choose not to renew, you keep the right to run the version you licensed, but no further patches or upgrades are provided.

Do unused support hours roll over? No. Support hours reset at each annual renewal.

What if I stop paying the annual fee? SWS is a perpetual license, so you may continue running the version you have indefinitely. Renewing reactivates patches, upgrades, and support.

What happens if I exceed my web service limit? You can upgrade to the next tier at any time. The upgrade price is the difference between your current tier and the new tier.

Do I need a PeopleSoft developer? For most use cases, no. SWS web services are created through configuration by a PeopleSoft power user who can write SQL. PeopleCode development is not required for standard SWS GET services.

How long does installation take? SWS installs as a standard PeopleSoft Application Designer project. Self-service installation typically takes under an hour. Enterprise and System Integrator tiers include guided installation via Zoom.

Does SWS modify delivered PeopleSoft objects? No. SWS is delivered as a standalone Application Designer project with its own custom objects. Zero delivered PeopleSoft objects are modified, which means SWS will not conflict with PeopleSoft updates or patches.

Can SWS support AI and LLM integrations? Yes. SWS exposes PeopleSoft data as clean REST APIs that return structured JSON, which is exactly what AI systems need for RAG (Retrieval-Augmented Generation) pipelines, LLM integrations, and chatbot backends.

Which tier is right for my institution? If you have a small number of targeted integrations, start with Junior. If you have an active integration program with multiple vendors, Pro is a strong fit. If you are pursuing digital transformation with broad data access needs, Enterprise gives you unlimited capacity. System Integrator is designed for consulting firms managing multiple client environments.

License Terms & Compliance

The pricing tiers above are governed by a written license agreement. Each tier includes the same compliance model: a perpetual, per-environment license, verified annually by a short customer-run self-audit.

  • License Agreement β€” scope of the license, restrictions, confidentiality of source, audit rights, and governing terms.
  • Self-Audit Procedure β€” the single SQL query you run once a year against the SWS configuration table to confirm your active web service count. Cedar Hills Group does not connect to your systems.
  • Copyright Notice β€” the header text embedded in every delivered SWS file and your obligation to preserve it.

Ready to Get Started?

Contact Chris Malek for a personalized consultation and to discuss which tier fits your institution’s needs.