This is the multi-page printable view of this section. Click here to print.
Documentation
- 1: Introduction to SWS
- 1.1: Introduction to SWS
- 1.2: Your First SWS Service
- 1.3: Which SWS Method Should I Use?
- 1.4: What is HTTP and REST
- 1.5: Recommended Tools
- 1.6: SWS Alternatives
- 2: Installing SWS
- 2.1: Installing SWS
- 2.2: Patching SWS
- 2.3: Release Notes
- 3: SWS Configuration
- 3.1: Configuration π
- 3.2: SWS Security Setup
- 3.3: SWS Caching
- 3.4:
- 4: PeopleSoft Query Language (PsoftQL)
- 4.1: Service Operation CHG_SWS_PSOFTQL
- 4.2: PsoftQL Syntax π
- 4.3: PsoftQL Validator/Builder
- 4.4: Campus Solutions PsoftQL - Examples
- 4.5: PsoftQL Operators & Filtering Examples
- 4.6: PeopleTools PsoftQL - Examples
- 4.7: Aggregate SQL Translation Examples
- 5: Road Map
- 6: Project Meta
- 7: Legal & Licensing
- 8: About the Project
- 9: Purchase SWS
1 - Introduction to SWS
1.1 - Introduction to SWS
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 calling the IB Service CHG_SWS (the URL prefix) which is handled by the Service Operation CHG_SWS_GET. This single service operation checks security, resolves parameters, executes the SQL, encodes the data, and returns it to the user β regardless of which path the caller used.
- SWS Get Services - Standard HTTP Clients - Service:
CHG_SWS, Service Operation:CHG_SWS_GET- 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
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
acmecorpas the vendor name in our URL path. We further added/people/emailto 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, andPrimaryEmail. 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
- Permission List: ACMECORP_SWS_USER
- OPRID: ACMECORP_SWS_USER

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?
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:#222Path 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
WHEREclause, 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
- Default choice:
CHG_SWSconfig-based service with PsoftQL syntax. - Reach for SQL inside a config when you need CSV output or SQL-side data translation.
- Reach for
CHG_SWS_PSOFTQLonly when the caller is a highly trusted internal iPaaS platform that can craft PsoftQL itself.
1.4 - What is HTTP and REST
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:
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.
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.
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.
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:
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.
Headers: Similar to request headers, response headers provide additional information about the response, such as the content type, cache-control directives, cookies, and more.
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.
- 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.
- 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
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.
curlis 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
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.
| Task | Minimum Estimated Person Hours | Maximum Estimated Person Hours |
|---|---|---|
| Gather Requirements | 5 | 10 |
| Write a technical specification | 5 | 10 |
| Technical Design Review | 2 | 10 |
| Create and unit test code | 8 | 40 |
| Functional Testing | 8 | 40 |
| Code Review | 2 | 10 |
| Code Migration | 2 | 4 |
| Bug Fixes | 4 | 10 |
| Totals | 36 Hours - $3,600 @ $100/Hour | 130 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
This section will describe how to install SWS in your PeopleSoft instance.
2.1 - Installing SWS
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.
- Development
- Test
- 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_
- Record Prefix -
- Exceptions:
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
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.
- Tools β Compare and Report β From File
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-DDcode 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
DESCRfield 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 noDESCRfield 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
pageNumberandrowLimitURL query-string parameters. The response meta now includespageNumber,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
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 calling the IB Service CHG_SWS (the URL prefix), handled by the Service Operation CHG_SWS_GET. That one service operation checks security, resolves parameters, executes the SQL, encodes the data, and returns it to the user β every SWS-configured path is served by the same handler.
Read more in the sections below.
3.1 - Configuration π
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.
PeopleSoft Integration Broker distinguishes a Service from a Service Operation. SWS uses both:
CHG_SWSβ the Service. This is the URL prefix that callers see (/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/your/path).CHG_SWS_GETβ the Service Operation. This is the PeopleCode handler that actually runs when a request comes in. All admin-configured GET paths are dispatched through this one handler.
When the docs say “configure security on the service operation,” they mean CHG_SWS_GET. When you see “the CHG_SWS URL,” that is the Service prefix in the URL. (CHG_SWS_PSOFTQL, the advanced caller-driven service operation, is documented separately.)
This section discusses configuring this service in detail in this section as well as the security model and how to configure API client users.
- We assume that you understand REST and HTTP Concepts. If you do not, please read the What is HTTP and REST section.
- There is a more advanced service operation for a more narrow user base. You probably do NOT need that but you can read about it here..
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 Types | SQL | PsoftQL |
|---|---|---|
| Accepts Client Parameters | Yes | Yes |
| Output JSON | Yes | Yes |
| Output XML | Yes | Yes |
| Output CSV | Yes | No |
| Nested Data parent/child data | No | Yes |
| Pagination | Yes (offset-based, via URL) | Yes (built-in, via request body) |
| Data Translation | Yes via SQL functions or Alias | No |
| Auto-Magic EFFDT Logic | None - Handled in SQL | EFFDT Logic automatically handled |
| Auto-Magic EFF_STATUS Logic | None - Handled in SQL | EFF_STATUS Logic automatically handled |
| Auto-Magic EFFSEQ Logic | None - Handled in SQL | EFFSEQ Logic automatically handled |
| Auto-Magic Field Exports | None - Handled in SQL | All 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
Data Access Models β CHG_SWS vs CHG_SWS_PSOFTQL
SWS ships two service operations, and they enforce data access in completely different ways. Knowing which model applies to a given endpoint determines what an administrator must do to grant or revoke access.
| Service operation | CHG_SWS_GET (this page) | CHG_SWS_PSOFTQL |
|---|---|---|
| Who chooses the SQL? | The PeopleSoft admin, in the SWS configuration row | The client, in the POST body |
| What gates access? | A permission-list grid on the configuration row | A whitelist table (C_SWS_REC_WL) keyed by permission list |
| Granularity | Per URL path | Per record name |
| Caller can request | Only the SQL the admin wrote at that path | Any record present in the whitelist for the caller’s permission list |
| Best for | Most third-party integrations | Trusted internal iPaaS platforms (MuleSoft, Snaplogic, Boomi) |
CHG_SWS (this page) β admin-defined paths. The admin writes the SQL or PsoftQL into a configuration row, attaches a list of permission lists allowed to call that path, and the caller GETs the URL. The caller cannot pick which record to read β the admin did that. Adding a record to the response means editing the SWS configuration. Granting a new partner access means adding their permission list to the configuration row’s permission-list grid.
CHG_SWS_PSOFTQL β caller-defined queries. The caller POSTs a PsoftQL body that names the records they want. Before running anything, the handler checks C_SWS_REC_WL to confirm every record in the request is whitelisted for the caller’s permission list. If any record is not whitelisted, the request is rejected with responseCode: 400 and the message “At least one record in your request is not whitelisted or is not a real record name.” β see Error Responses. Adding a record means inserting a row into C_SWS_REC_WL. Granting a new partner means giving their permission list whitelist entries for the records they need. The C_SWS_REC_WL table is described in the PsoftQL Web Service page.
The two models are independent. A given OPRID can have access to a curated set of CHG_SWS paths and a separate whitelist for CHG_SWS_PSOFTQL β those settings do not overlap. If you only ever expose data through CHG_SWS, the whitelist table doesn’t matter.
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
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.
- If “Psoft-QL request format type was chosen then a long text box will show up called. Psoft-QL Text (JSON)
- 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=Non 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,CSVandXMLencoding. - 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.
The Parameters Grid
The Parameters Grid binds values from the inbound HTTP request to placeholders in your SQL or PsoftQL. Each row in the grid declares one parameter that the SWS handler will accept and substitute at run time. If your SQL has no :name bind variables (or your PsoftQL has no {{name}} placeholders), this grid stays empty and the handler simply runs the statement as-is.
Anatomy of a Parameters Grid row
Each row carries the fields below. The exact column labels match the labels on the PeopleSoft setup page.
| Column | What to enter |
|---|---|
| Parameter Name | The name you will reference in the statement. For SQL, write :paramName in the SQL Statement Text. For PsoftQL, write {{paramName}} inside sqlWhereClause or a path segment. |
| Source | Where the value comes from. The two supported sources are URL Path Segment and Query String. URL Path Segment is for {paramName} tokens in the URL Path field; Query String is for ?paramName=value on the request URL. |
| Required | If checked and the parameter is absent at request time, the handler short-circuits and returns an error without running your SQL. Leave unchecked for optional filters whose defaults are handled inside the SQL. |
| Default Value | Optional. Used when the parameter is not supplied and Required is unchecked. Skip when the SQL itself handles the default (e.g. WHERE EFFDT <= COALESCE(:asof, %CURRENTDATEIN)). |
| Data Type / Length | Informational on the grid; the handler does not coerce types. The bind happens as a string and your SQL’s column type drives any implicit conversion. Use this column as documentation for whoever maintains the configuration. |
Wiring a URL path parameter β step by step
Goal: serve GET /acmecorp/people/{emplid} and substitute {emplid} into the SQL.
On the URL Path field, enter
acmecorp/people/{emplid}. The braces are literal β they tell SWS to capture whatever segment appears in that position at request time.In the SQL Statement Text, reference the value as
:emplid:SELECT A.EMPLID, A.NAME FROM %TABLE(PERSON_NAME) A WHERE A.EMPLID = :emplidAdd one row to the Parameters Grid:
Parameter Name Source Required Default Value emplidURL Path Segment yes (blank) Save. At request time,
GET .../acmecorp/people/KU0001runs the SQL with:emplidbound to'KU0001'. The actual SQL executed appears in the response’smeta.finalSQLβ useful for verifying the substitution worked.
Wiring a query-string parameter
Goal: optional ?activeOnly=Y filter on the same endpoint.
Extend the SQL:
SELECT A.EMPLID, A.NAME, A.ACCTLOCK FROM %TABLE(PSOPRDEFN) A WHERE (:activeOnly = 'N' OR A.ACCTLOCK = 0)Add a Parameters Grid row:
Parameter Name Source Required Default Value activeOnlyQuery String no NGET .../usersreturns everyone (default'N');GET .../users?activeOnly=Yfilters to non-locked accounts. The response’smeta.QueryStringechoes back the literal query string the client supplied, which is handy for confirming the value was actually received.
Parameter binding gotchas
- SQL bind variables are strings on the wire. If you need numeric semantics in the SQL, cast explicitly:
WHERE A.PERSON_NUM = TO_NUMBER(:personNum)on Oracle. - Names are case-sensitive in PsoftQL
{{...}}substitution but match by position in SQL:namebinding. Pick a consistent convention (lowerCamelCase is common) and stick with it. - Path-segment names must match between the URL Path field and the grid. A typo in either silently treats the parameter as absent.
- Never concatenate parameter values into the SQL string yourself. Always use
:namebinds β SWS handles SQL injection prevention only when you use the binding path.
The Output Fields Grid
The Output Fields Grid lives only on SQL configurations. It maps each SQL column position to the field name that appears in the response. The grid serves three encoders from one source of truth:
- JSON β the grid value becomes the property name in each row object
- XML β the grid value becomes the element tag inside each
<row> - CSV β the grid value becomes the column header in the first row
Filling out the grid
The grid has one row per SELECT column, in the same order as the SQL. If your SQL is:
SELECT A.EMPLID, A.NAME, A.EMAIL_ADDR
FROM %TABLE(PERSON) A
WHERE A.EMPLID = :emplid
then the grid has three rows:
| Column Number | Output Field Name |
|---|---|
| 1 | employeeId |
| 2 | fullName |
| 3 | emailAddress |
The number column auto-increments β what you control is the Output Field Name for each position. Names can be anything the encoders accept; lowerCamelCase or snake_case both work, but pick one and stay consistent across your endpoints.
What happens if I skip a row?
If the grid has fewer rows than the SQL has columns, the missing columns fall through to the original PeopleSoft field name in upper case (EMPLID, NAME, etc.). If the grid has more rows than the SQL has columns, the extras are ignored. Avoid both β match the row count exactly so the configuration documents the contract clearly.
Re-ordering columns
Re-ordering the grid does not re-order the response. The position number is what binds a grid row to a SELECT column. To change column order in the response, change the SELECT list in the SQL and renumber the grid to match.
Why no Output Fields Grid for PsoftQL?
PsoftQL responses are nested PeopleSoft records, not flat rowsets. The property names come straight from the record’s field names β there is nothing to alias. If you need different output field names from a PsoftQL response, transform the response in your client or switch the configuration to SQL.
Worked Example β SQL Configuration with a Path Parameter
The configuration walkthrough above used a PsoftQL example. This section shows the same idea using SQL β useful when you want CSV output, SQL functions, or a hand-tuned WHERE clause. The endpoint demonstrated here is deployed on our public demo system; you can reproduce the same configuration in your own database.
The configuration
| Field | Value |
|---|---|
| URL Path | test/sql/security/users/{oprid} |
| Request Format Type | SQL |
| SQL Statement Text | SELECT A.OPRID, A.ACCTLOCK FROM %TABLE(PSOPRDEFN) A WHERE OPRID = :oprid |
| Parameters Grid | One row: name oprid, source = URL path segment |
| Output Fields Grid | OPRID β oprid, ACCTLOCK β locked |
| Default Encoding | JSON |
| Include metadata | Checked |
| Allowed Permission List | (whichever permission list represents your API client; the demo uses CHG_PSOFTLENS_API_USER) |
Three notes on the SQL above:
%TABLE(PSOPRDEFN)is a PeopleSoft meta-SQL macro that resolves to the actual physical table name (PSOPRDEFN) at run time. Using it lets your configuration survive renames between releases.:opridis the SQL-bind form of the path parameter. SWS routes the value the client supplied in the{oprid}URL segment into the bind variable. SWS handles parameter substitution safely; do not concatenate request values directly into the SQL string.- The Output Fields Grid renames
OPRIDtoopridandACCTLOCKtolockedso the response uses lower-case, vendor-friendly names rather than the underlying record’s all-caps PeopleSoft field names.
The HTTP call
GET http://your-ib-host/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/test/sql/security/users/PS
Authorization: Basic ...redacted...
Accept: application/json
JSON response
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": [
{
"oprid": "PS",
"locked": "0"
}
],
"errors": "",
"meta": {
"rowCount": "1",
"sqlIDExecuted": "2d43c8aa-40b2-4c39-9b2a-4a115804c685",
"success": "True",
"pageNumber": "1",
"rowLimit": "9999999",
"URLPath": "test/sql/security/users/PS",
"finalSQL": "SELECT A.OPRID, A.ACCTLOCK FROM %TABLE(PSOPRDEFN) A WHERE OPRID = 'PS'",
"productVersion": "2026-04-20",
"toolsVer": "8.61.03",
"currentUser": "CHG_PSOFTLENS_API_USER",
"dbname": "CS92DEV",
"dbType": "ORACLE"
}
}
The two interesting fields in meta:
finalSQLshows the SQL after parameter substitution βWHERE OPRID = 'PS'. Use this when debugging “why didn’t I get the rows I expected?”: you can copyfinalSQLstraight into a SQL client and run it yourself.sqlIDExecutedis a per-execution GUID that links the response back to log entries inC_SWS_RUN_LOGfor audit and troubleshooting.
Same endpoint, CSV encoding
Change the Accept header and SWS swaps the encoder. The response body becomes a CSV document and the metadata moves into x-* HTTP response headers (CSV has no place for a nested meta block):
GET http://your-ib-host/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/test/sql/security/users/PS
Authorization: Basic ...redacted...
Accept: text/csv
HTTP/1.1 200 OK
Content-Type: text/csv; encoding=UTF-8
x-rowCount: 1
x-pageNumber: 1
x-URLPath: test/sql/security/users/PS
x-sqlIDExecuted: 2d43c8aa-40b2-4c39-9b2a-4a115804c685
x-success: True
"oprid","locked"
"PS","0"
The header row uses the names from the Output Fields Grid (oprid, locked), confirming that the same grid feeds JSON property names, XML element names, and CSV header names from a single source of truth.
Pagination over a SQL endpoint
The exact same configuration paginates automatically if the client adds query-string parameters β no setup change required. See Pagination for SQL endpoints below for the full parameter list. Quick example, page 2 with 3 rows per page against test/sql/allusers:
GET .../CHG_SWS/test/sql/allusers?pageNumber=2&rowLimit=3
Accept: application/json
{
"data": [
{ "oprid": "ASHUE", "emplid": "" },
{ "oprid": "AZIGLAR","emplid": "" },
{ "oprid": "BDAVIS", "emplid": "" }
],
"errors": "",
"meta": {
"rowCount": "3",
"pageNumber": "2",
"rowLimit": "3",
"QueryString": "pageNumber=2&rowLimit=3",
"finalSQL": "select oprid, emplid from psoprdefn"
}
}
Notice that finalSQL is the original SQL β pagination is layered on top by the handler, not by rewriting the statement. The client controls page size and offset; the admin controls what SQL runs and what rows it can see.
SQL Response Metadata
Every SQL-type response (JSON or XML) carries a meta block; CSV responses surface the same values as x-* HTTP headers. The PsoftQL response page has a companion reference covering the fields shared between SQL and PsoftQL responses (toolsVer, dbname, psftTransactionId, etc.). The table below covers the SQL-specific fields you only see on CHG_SWS_GET responses.
| Property | Type | Use |
|---|---|---|
finalSQL | string | The SQL that ran, after parameter substitution. Copy/paste this into a SQL client to reproduce the result by hand. Indispensable for debugging “why didn’t I get the rows I expected?”. |
sqlIDExecuted | string | Per-execution GUID. Links the response back to the audit row written to C_SWS_RUN_LOG and to any application-server log entries that mention it. |
rowCount | string | Number of rows in the response after any rowLimit cap. Quoted as a string β older PeopleTools quirk; parse to integer in your client. |
URLPath | string | Path segment that resolved to this configuration row (e.g. "test/sql/security/users/PS"). Useful when one OPRID has access to many similar paths and you want to confirm which one served the response. |
QueryString | string | Raw query string from the request (or the literal "NULL" when no query string was supplied). |
pageNumber | string | Page index the client requested. Quoted as a string for the same reason as rowCount. |
rowLimit | string | Effective page size β the lower of the client’s ?rowLimit=N and the configuration’s row limit. |
success | string | "True" or "False". Redundant with HTTP status but the only success indicator inside the CSV x-* headers. |
debugMessage | string | Handler debug trace when the configuration is in debug mode. Empty otherwise. Note the singular spelling β distinct from PsoftQL’s plural debugMessages. |
For CSV responses the same fields are emitted as response headers with an x- prefix: x-finalSQL, x-sqlIDExecuted, x-rowCount, x-URLPath, etc. The HTTP header field names preserve the original camelCase, which is unusual but matches what callers see on the wire.
A few specific tips:
- Treat
metaas informational, not a contract. Field set varies by PeopleTools version. Newer builds emitproductVersion; older ones emitapiVersion. Code defensively. - Don’t log
finalSQLto public sinks. It can contain values from path parameters and query strings β including ones a caller might consider sensitive (EMPLID, OPRID, etc.). Treat it like a SQL trace. psftTransactionIdis your support handle. Anything you can’t reproduce β capture this GUID and PeopleSoft support can correlate it across app-server logs, IB monitor, and the SWS run log.
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 parameter | Type | Default | Meaning |
|---|---|---|---|
rowLimit | integer | Setup-configured Row Limit value | The maximum number of rows returned on a single page. |
pageNumber | integer | 1 | 1-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"
}
}
pageNumberandrowLimitecho the effective values used for this request.nextPageNumberis 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 headersx-pageNumber,x-rowLimit, andx-nextPageNumber(matching the existingx-rowCount,x-success, and otherx-*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
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_GETservice operation. If the OPRID does not have access, the IB will return an error.
- The authenticated OPRID must have access to the
- 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_USERfor this purpose.
- You can use the role
- 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-floodfor 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.
- Create a complex randomly generated password. We will use
- Give OPRID
Z_TEST_API_USERthe roleCHG_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_USERand assign it theZ_TEST_API_USERpermission list. - Grant user
Z_TEST_API_USERthe newZ_TEST_API_USERrole.
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.
- For our three internal systems that may look like:
- 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
- ROLE: CHG_SWS_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
- ROLE: CHG_SWS_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
- ROLE: CHG_SWS_USER
- OPRID: Z_STELLAR_WIND_API_USER
- For our three internal systems that may end up looking like this:
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
- MENU:
- Role:
CHG_SWS_ADMIN- Permission List:
CHG_SWS_ADMIN
- Permission List:
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_POSTshould 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
SWS response caching is provided by the PeopleSoft RESTListeningConnector servlet β SWS does not implement its own cache, it only opts an endpoint in. The cache has several non-obvious behaviors that can leak data between callers or serve stale data indefinitely. Read the companion PeopleSoft REST Caching chapter in the Integration Broker book for the underlying mechanics; this page covers how it applies specifically to SWS.
How SWS uses the PeopleTools REST cache
When you set Minutes to Cache Response to a non-zero value on an SWS configuration row, the handler adds a Cache-Control: max-age=<N*60> header to the response. The PeopleTools RESTListeningConnector servlet then stores the full response in memory and replays it for subsequent matching requests until the TTL expires. The cache lives in the PIA web-server JVM, not in PeopleSoft application-server memory β restarting PIA empties it; restarting the app server does not.
No code change is required to opt in. Set a non-zero value on the configuration row, save, and the very next request from that endpoint will populate the cache.
What gets cached, and what the cache key actually is
The cache key is the full request URL β that is, path and query string β combined with the Accept header. Two requests are treated as cache hits only when all three match exactly.
| Component | Part of cache key? | Notes |
|---|---|---|
URL path (e.g. /acmecorp/people/PS) | yes | Different path = different cache entry. {emplid} path parameters create one entry per distinct value. |
Query string (e.g. ?rowLimit=50) | yes | ?rowLimit=10 and ?rowLimit=20 are independent cache entries. So is the absence of any query string. |
Accept header | yes | JSON, XML, and CSV responses are cached independently. Three different Accept values populate three cache entries. |
Authorization header (basic-auth user) | no | This is the central caching gotcha. Two different callers hitting the same URL get the same cached response β see below. |
| Request body (POST endpoints) | no | The REST cache is GET-oriented. CHG_SWS_PSOFTQL (a POST endpoint) effectively does not benefit from this cache for variable input. |
Choosing a TTL
The right cache duration is the longest interval during which a stale response is still acceptable to the caller. Some patterns we’ve seen work well:
| Endpoint pattern | Recommended TTL | Why |
|---|---|---|
| XLAT / setup lookups (subjects, campuses) | 60 minutes (or 1440 / 24h) | These change on a release cadence β minutes-old data is fine. |
| Reference tables editable only by power users | 5β30 minutes | Long enough to absorb burst traffic, short enough that an admin’s edit shows up within the hour. |
| Reporting / dashboard rollups | 5β15 minutes | Dashboards typically refresh on this cadence anyway; caching aligns with the refresh budget. |
| Real-time-ish data (current term enrollments) | 1β2 minutes | Use only when the source data changes slower than the TTL. |
Per-user data (uses %OPERATOR / %EMPLOYEEID) | 0 (disabled) | Caching across users leaks data. See the security alert below. |
| Cheap queries (<100ms server time) | 0 (disabled) | The cache itself adds overhead. If the query isn’t slow, don’t cache. |
There is no upper limit enforced by SWS. Practical ceiling: anything beyond a few hours starts feeling like a static asset, at which point you should consider regenerating the data into a separate table refreshed by a scheduled job rather than caching an unbounded query.
No manual cache invalidation
There is no SWS or PeopleSoft UI to flush the REST cache. Your options when cached data is wrong are:
- Wait out the TTL. The cache entry expires naturally and the next request re-runs the SQL.
- Bounce PIA. Restarting the PeopleSoft web server tier empties the JVM cache. This is heavy-handed but effective.
- Change the URL. Adding a cache-busting query-string parameter (
?_v=2) creates a new cache entry rather than replacing the old one β works as a per-caller workaround when you can change the integration partner’s URL.
Choose your TTL with the assumption that you cannot intervene mid-window.
Security: the cross-user data-leak risk
The cache key does not include the caller’s OPRID. Two callers hitting the same URL within the TTL window receive the same response, even when:
- They were authenticated as different OPRIDs.
- They belong to different permission lists.
- The underlying SQL uses
%OPERATORor%EMPLOYEEIDand would otherwise return different rows per user.
This means that an endpoint where caller A hits /student/grades and gets caller A’s grades will, if cached, return caller A’s grades to caller B’s next call as well.
Never enable caching on an SWS configuration whose SQL contains %OPERATOR, %EMPLOYEEID, or any other per-caller binding β including SQL that filters by the caller’s row-level-security setid. The handler does not warn you about this; nothing in the UI prevents it. The first cross-user request after the cache populates will return the wrong person’s data.
Safe caching candidates: endpoints whose response depends only on the URL, query string, and database state β never on which OPRID is calling.
Detecting whether a response was served from cache
There is no SWS-emitted “cache hit” header. Inspect these signals instead:
meta.responseDTTMin JSON/XML responses (orx-requestTimefor CSV) is the timestamp the response was originally assembled. On a cache hit it stays at the populate-time value, not the current wall clock. If two calls 30 seconds apart return identicalresponseDTTMstrings, the second was a cache hit.meta.psftTransactionIdis also frozen at populate time. Identical transaction IDs across calls = same cached entry.- Wall-clock latency drops sharply on a hit β typically from hundreds of milliseconds (real SQL execution) to single-digit milliseconds (servlet returning cached bytes).
Combine the three: if responseDTTM and psftTransactionId match across two calls and latency is sub-10ms, you’re seeing a cache hit.
See also
- Caching limitations β same restrictions listed alongside other product boundaries.
- PeopleSoft REST Caching β Integration Broker book β the underlying servlet mechanics.
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)
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
This is a more advanced web service that is meant for very trusted integration platforms like your internal MuleSoft, Snaplogic or Boomi integration platform. If you desire to create an SWS service for a non-trusted third party then you should instead use the SWS GET Handler
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.

Letting a Caller Discover Their Own Whitelist
For advanced use cases, it is often helpful for the client to be able to see what they have access to. Add C_SWS_REC_WL itself to the caller’s whitelist and they can query the table to introspect their own permissions.
The whitelist query uses the same PsoftQL syntax as any other record:
POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json
{
"rowLimit": 5,
"records": [
{ "recordName": "C_SWS_REC_WL" }
]
}
Response β captured live from our demo system, authenticated as an OPRID whose permission list is CHG_PSOFT_LENS_API_USER:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"C_SWS_REC_WL": {
"objectType": "record",
"objectName": "C_SWS_REC_WL",
"fields": [
{ "rowNumber": 1, "RECNAME": "ACAD_GROUP_TBL", "CLASSID": "CHG_PSOFT_LENS_API_USER", "LASTUPDDTTM": "2026-04-20-18.30.05.000000", "LASTUPDOPRID": "PS" },
{ "rowNumber": 2, "RECNAME": "ACAD_ORG_TBL", "CLASSID": "CHG_PSOFT_LENS_API_USER", "LASTUPDDTTM": "2026-04-20-17.38.00.000000", "LASTUPDOPRID": "PS" },
{ "rowNumber": 3, "RECNAME": "ACAD_PLAN", "CLASSID": "CHG_PSOFT_LENS_API_USER", "LASTUPDDTTM": "2026-04-20-03.08.37.000000", "LASTUPDOPRID": "PS" },
{ "rowNumber": 4, "RECNAME": "ACAD_PROG", "CLASSID": "CHG_PSOFT_LENS_API_USER", "LASTUPDDTTM": "2026-04-01-05.22.22.000000", "LASTUPDOPRID": "PS" },
{ "rowNumber": 5, "RECNAME": "ACCESS_GRP_LANG", "CLASSID": "CHG_PSOFT_LENS_API_USER", "LASTUPDDTTM": "2026-01-22-15.25.47.000000", "LASTUPDOPRID": "PS" }
]
}
},
"responseCode": 200,
"pageNumber": 1,
"nextPageNumber": 2
}
Three things to notice:
- The
RECNAMEcolumn is the whitelisted record name. This is the list the caller can use in PsoftQLrecords[].recordName. - The
CLASSIDcolumn is the permission list that owns the entry. Note that even though the request did not filter byCLASSID, every returned row carries the caller’s own permission list value. The whitelist query is implicitly filtered to the caller’s permission lists β a caller cannot enumerate another tenant’s whitelist by queryingC_SWS_REC_WL. nextPageNumber: 2confirms there is more data to page through. StandardpageNumberpagination applies to the whitelist exactly as it does to any other record.
You can also filter explicitly with criteriaFields if the caller’s OPRID is granted multiple permission lists and you only want to see one slice:
{
"rowLimit": 100,
"records": [
{
"recordName": "C_SWS_REC_WL",
"criteriaFields": [
{ "fieldName": "CLASSID", "fieldValue": "CHG_PSOFT_LENS_API_USER", "operator": "=" }
]
}
]
}
The CLASSID in the whitelist is the permission list name, which is not necessarily the same string as the OPRID or the role name. In the demo example above the OPRID is CHG_PSOFTLENS_API_USER (no internal underscore) but the permission list is CHG_PSOFT_LENS_API_USER (with underscore). Filtering CLASSID with the wrong spelling silently returns zero rows β see Silent behaviours on the syntax page. Look the permission list name up directly in PeopleSoft Security β Permission Lists rather than guessing from the OPRID.
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
paginationto 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_STATUSfield 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
dataelement. The JSON structure of what is returned nested inside thedataelement 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.
- This web service only pulls back records where
4.2 - PsoftQL Syntax π
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.
We are using the more advanced PsoftQL Web Service but the same syntax is configured in SWS. We also assume that the tables here are “Whitelisted” which is covered in the PsoftQL Web Service documentation.
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
- PSCLASSDEFN - The permission list definition.
- PSROLECLASS - Defines Permission lists (CLASSID) assigned to a role.
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 pagenoEffectiveDateLogic- (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 whereEFF_STATUS = 'I'.noEffectiveSequenceLogic- (boolean, default: false) Optional. Only applies to records that carry anEFFSEQfield (for exampleJOBand other HR records that allow multiple effective-dated rows on the same day). When false (the default), the row with the highestEFFSEQvalue for the chosenEFFDTis returned. Set to true to return everyEFFSEQrow for that date β useful when auditing same-day correction history or building point-in-time snapshots that need to see every intermediate value, not just the final one.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 theparentRecordNameblank to designate the ‘root parent record’. There can only be one record listed withparentRecordNameblank. “,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 therecordNameyou 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.useNonKeyFieldsInJoin(boolean, optional for child records only, default: false) When PsoftQL auto-joins a child to its parent it normally considers only the child’s key fields. Set this to true to let non-key fields participate in the join discovery. This is needed in the rare case where a child record shares a meaningful non-key field with the parent (for example, a denormalized FK that isn’t part of the child’s key list). Most standard PeopleSoft tables join correctly on keys alone, so leave this off unless you have inspected the record and confirmed the join needs a non-key field. If you also want to override the join completely, preferdoNotAutoJoinToParentplus an explicitjoinFieldsarray.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 setdoNotAutoJoinToParent = trueif 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 andLIKEwildcard pattern.fieldName(string) Field name to limit. It must exist on the record.fieldValue(string) The value to limitoperator(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: trueonly 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 - eitherASC(ascending) orDESC(descending). Defaults toASCif not specified.
Field-Level Options β Worked Examples
The examples below cover the per-record options that most callers reach for once they move past the “give me every column” defaults. Each shows a focused request and the relevant slice of the response.
includeDescriptionsFor β translate selected codes only
includeAllDescriptions: true is convenient during development but expensive at run time because the handler probes every coded field on the record. In production, name the few fields whose translations you actually want:
{
"rowLimit": 2,
"records": [
{
"recordName": "PSROLEDEFN",
"includeDescriptionsFor": ["ROLETYPE", "ROLESTATUS"],
"excludeFields": [
"VERSION", "QRYNAME", "RECNAME", "FIELDNAME",
"PC_EVENT_TYPE", "QRYNAME_SEC", "PC_FUNCTION_NAME",
"ROLE_PCODE_RULE_ON", "ROLE_QUERY_RULE_ON", "LDAP_RULE_ON",
"ALLOWNOTIFY", "ALLOWLOOKUP", "DESCRLONG"
]
}
]
}
Response β only ROLETYPE_description and ROLESTATUS_description appear; the rest of the coded fields stay un-translated:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": [
{
"rowNumber": 1,
"ROLENAME": "ACM Administrator",
"ROLETYPE": "U",
"ROLETYPE_description": "User List",
"DESCR": "ACM Administrator",
"ROLESTATUS": "A",
"ROLESTATUS_description": "Active",
"LASTUPDDTTM": "2021-05-19-07.38.09.805472",
"LASTUPDOPRID": "PPLSOFT"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2
}
excludeFields β drop sensitive or noisy columns at the source
excludeFields filters before serialization, so the dropped columns never leave PeopleSoft. Always use it for hashed password material on PSOPRDEFN, and for any sensitive payroll/SSN columns on JOB/PERSON tables. See the combined-criteria example on the Operator Examples page for a PSOPRDEFN query that strips OPERPSWD, PTOPERPSWDV2, OPERPSWDSALT, and ENCRYPTED.
effectiveDateOverride β point-in-time queries
Without effectiveDateOverride, PsoftQL filters effective-dated records to the row that is current today. Override it to ask “what was the state of this data as of date X?” β useful for reconciling reports, replaying historical decisions, or matching what a user would have seen in a Classic page on a specific date.
In this example CRSE_ID = '000101' has two effective-dated rows in the demo database: one with EFFDT = 1900-01-01 and one with EFFDT = 2009-01-01.
Default behaviour β returns the 2009 row because today is later:
{
"rowLimit": 1,
"records": [
{
"recordName": "CRSE_CATALOG",
"criteriaFields": [
{ "fieldName": "CRSE_ID", "fieldValue": "000101", "operator": "=" }
]
}
]
}
{
"data": {
"CRSE_CATALOG": {
"fields": [
{ "rowNumber": 1, "CRSE_ID": "000101", "EFFDT": "2009-01-01", "DESCR": "Chemistry 1" }
]
}
},
"responseCode": 200
}
With effectiveDateOverride: "2005-01-01" β returns the 1900 row, which was the row in effect on Jan 1, 2005:
{
"rowLimit": 1,
"effectiveDateOverride": "2005-01-01",
"records": [
{
"recordName": "CRSE_CATALOG",
"criteriaFields": [
{ "fieldName": "CRSE_ID", "fieldValue": "000101", "operator": "=" }
]
}
]
}
{
"data": {
"CRSE_CATALOG": {
"fields": [
{ "rowNumber": 1, "CRSE_ID": "000101", "EFFDT": "1900-01-01", "DESCR": "Chemistry 1" }
]
}
},
"responseCode": 200
}
effectiveDateOverride is global to the request: every effective-dated record in records uses it. Date format is ISO YYYY-MM-DD. To bypass effective-date filtering entirely (return all historical rows), use noEffectiveDateLogic: true instead.
joinFields β joining records whose field names don’t line up
The auto-join logic finds the common key fields between parent and child. That covers almost every standard PeopleSoft parent/child relationship β for example, PSROLEDEFN.ROLENAME joins cleanly to PSROLECLASS.ROLENAME. The auto-join breaks down when the related fields are spelled differently between parent and child. The classic case is PSOPRDEFN.OPRID (parent) β PSROLEUSER.ROLEUSER (child) β same semantic value, different field name.
Use joinFields to spell the relationship out:
{
"rowLimit": 1,
"records": [
{
"recordName": "PSOPRDEFN",
"excludeFields": [
"OPERPSWD", "PTOPERPSWDV2", "OPERPSWDSALT", "ENCRYPTED",
"VERSION", "USERIDALIAS", "SYMBOLICID"
],
"criteriaFields": [
{ "fieldName": "OPRID", "fieldValue": "AADAMS", "operator": "=" }
]
},
{
"recordName": "PSROLEUSER",
"parentRecordName": "PSOPRDEFN",
"joinFields": [
{ "parentField": "OPRID", "childField": "ROLEUSER" }
]
}
]
}
The response nests every role assigned to AADAMS under the parent record:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSOPRDEFN": {
"objectType": "record",
"objectName": "PSOPRDEFN",
"fields": [
{
"rowNumber": 1,
"OPRID": "AADAMS",
"OPRDEFNDESC": "Andrew Adams",
"LASTUPDDTTM": "2021-09-08-05.01.17.440969",
"LASTUPDOPRID": "PPLSOFT",
"PSROLEUSER": {
"objectType": "record",
"objectName": "PSROLEUSER",
"fields": [
{ "ROLEUSER": "AADAMS", "ROLENAME": "CS - Student", "DYNAMIC_SW": "N" },
{ "ROLEUSER": "AADAMS", "ROLENAME": "CS - Student Applicant","DYNAMIC_SW": "N" },
{ "ROLEUSER": "AADAMS", "ROLENAME": "EOPP_USER", "DYNAMIC_SW": "N" }
]
}
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1
}
For cases where the parent/child relationship doesn’t share any common key and PsoftQL cannot discover a join on its own, also set doNotAutoJoinToParent: true on the child so the handler does not attempt its key-matching pass.
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).%currentdateincan 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": 3,
"records": [
{
"recordName": "PSPROJECTDEFN",
"sqlWhereClause": "LASTUPDDTTM is not null",
"orderByFields": [
{
"fieldName": "LASTUPDDTTM",
"sortOrder": "DESC"
}
]
}
]
}
The response returns the three most recently updated projects first:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSPROJECTDEFN": {
"objectType": "record",
"objectName": "PSPROJECTDEFN",
"fields": [
{
"rowNumber": 1,
"PROJECTNAME": "CHG_CI_TEST",
"LASTUPDDTTM": "2026-05-08-23.22.42.036849"
},
{
"rowNumber": 2,
"PROJECTNAME": "CMALEK_NODE",
"LASTUPDDTTM": "2026-04-23-02.15.14.806879"
},
{
"rowNumber": 3,
"PROJECTNAME": "CHG_TOKEN_GEN_POC",
"LASTUPDDTTM": "2026-04-22-16.53.29.852062"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2
}
Flip the same request to "sortOrder": "ASC" and the first three rows become the oldest projects in the database β useful proof that orderByFields is honored end-to-end and that pagination follows the custom order, not the record’s key order.
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": 2,
"records": [
{
"recordName": "ACAD_ORG_TBL"
}
]
}
The response contains the next two rows of the underlying result set, and again carries a nextPageNumber until you reach the final page. Note that rowNumber is the absolute row index across the entire result set β it continues counting from page to page rather than restarting at 1 each page.
{
"data": {
"ACAD_ORG_TBL": {
"objectType": "record",
"objectName": "ACAD_ORG_TBL",
"fields": [
{
"rowNumber": 3,
"ACAD_ORG": "ACCOUNTING",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Accounting",
"DESCRSHORT": "Accounting",
"DESCRFORMAL": "Accounting",
"INSTITUTION": "GLAKE",
"CAMPUS": "MAIN",
"MANAGER_ID": "",
"INSTR_EDIT": "P",
"CAMPUS_EDIT": "N",
"SUBJECT_EDIT": "N",
"COURSE_EDIT": "N"
},
{
"rowNumber": 4,
"ACAD_ORG": "ACCT",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Accounting",
"DESCRSHORT": "Acct",
"DESCRFORMAL": "Accounting",
"INSTITUTION": "PSCCS",
"CAMPUS": "",
"MANAGER_ID": "",
"INSTR_EDIT": "P",
"CAMPUS_EDIT": "N",
"SUBJECT_EDIT": "N",
"COURSE_EDIT": "N"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 2,
"apiVersion": "2023-03-14",
"nextPageNumber": 3
}
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"
}
Response Structure Reference
Every PsoftQL response β successful or not β has the same outer shape. This section names every property you will see, so you know which ones to depend on in client code and which ones are diagnostic-only.
Top-level properties
| Property | Type | When present | Meaning |
|---|---|---|---|
data | object | Always | Container for the actual record data, keyed by record name. Empty {} on error. |
responseCode | integer | Always | Application-level status. 200 = success, 400 = handler-detected validation error. Check this before parsing data. See Error Responses. |
errorMessages | string | Always | Empty string on success; a single human-readable error string when responseCode is 400. Reserved name; production code should not depend on the exact wording. |
pageNumber | integer | Standard queries | The page you just received. Equals the pageNumber you sent (defaults to 1). |
nextPageNumber | integer | Standard queries when more pages exist | The next page to request. Absence of this property is how you detect “last page” or “zero results.” |
apiVersion | string | Older builds | Legacy version stamp. Newer builds emit productVersion instead. Treat as informational; do not gate behavior on it. |
productVersion | string | Newer builds | Replaces apiVersion. Build/release stamp of the SWS code that served the request. |
isAggregate | boolean | Aggregate queries only | Always true when present. Echoes back the request’s isAggregate flag so the caller can tell aggregate responses apart from standard ones at a glance. |
totalGroups | integer | Aggregate queries only | Count of rows in data.{RECORD}.results β i.e. the number of distinct GROUP BY buckets returned. With rowLimit set, this is the truncated count. |
Inside data.{recordName} (standard queries)
| Property | Type | Meaning |
|---|---|---|
objectType | string | Always "record" for standard queries; "aggregateResult" for aggregate queries. |
objectName | string | The record name. Echoes what you asked for in recordName. |
fields | array | The result rows. Empty array [] means “filter matched zero rows” (not an error). |
Each row in fields carries the record’s field values plus a few synthetic properties:
| Property | Type | When present |
|---|---|---|
rowNumber | integer | Standard queries. Absolute row index across the whole result set β continues counting page-to-page rather than restarting per page. |
{FIELD}_description | string | When includeAllDescriptions: true or when {FIELD} is in includeDescriptionsFor. Decoded XLAT or prompt-table value. |
{FIELD}_defaultLabel | string | When includeAllFieldLabels: true. Default field label from the record definition. |
{FIELD}_fieldType | string | When includeFieldTypes: true. PeopleSoft data type (e.g. CHAR, NUMBER). |
{FIELD}_fieldLength | integer | When includeFieldTypes: true. Field length declared on the record. |
{FIELD}_isKey | boolean | When includeKeyFieldIndicators: true. Only emitted for fields that are part of the record’s key list; not emitted when false. |
{ChildRecord} | object | When a child record is configured. Nested { objectType, objectName, fields: [...] } object β see the parent/child examples. |
Inside data.{recordName} (aggregate queries)
Aggregate responses use a different shape β see Aggregate Response Structure for the comparison table.
The meta block
The meta block carries diagnostic and environment information. It is present in most responses but its contents vary between builds β older PeopleTools versions emit fewer fields, and the handler may add fields in future releases. Treat anything in meta as informational; never gate business logic on it.
| Property | Type | Use |
|---|---|---|
toolsVer | string | PeopleTools version that served the request (e.g. "8.61.03"). Useful when reproducing issues across environments. |
dbname | string | Database name (e.g. "CS92DEV"). Sanity-check when configurations span multiple databases. |
dbType | string | "ORACLE" or "MICROSOFT" β drives the SQL dialect SWS generated (e.g. FETCH FIRST vs TOP). |
serverTimeZone | string | App server’s local time zone. The responseDTTM field is in this zone, not UTC. |
responseDTTM | string | Server timestamp when the response was assembled, format YYYY-MM-DD-HH.MM.SS.NNNNNN. |
psftTransactionId | string | Per-call GUID issued by PeopleSoft IB. Use this when filing support tickets β it links the response to logs in PSAPPSRV and the IB monitor. |
currentUser | string | OPRID of the authenticated caller. Older builds only; newer builds omit it. |
serverDirectory | string | App-server install path. Older builds only. |
debugMessages | string | Empty unless the request sent isDebugMode: true; then carries handler trace output. |
For SQL-configured endpoints (CHG_SWS_GET) the meta block has additional SQL-specific fields like finalSQL, sqlIDExecuted, rowCount, URLPath, and QueryString β those are documented in the SQL response metadata reference on the configuration page.
Error Responses
PsoftQL surfaces failures in two distinct layers. Knowing which layer rejected a request determines how you should react in your client code.
Layer 1 β PeopleSoft Integration Broker (transport / auth)
The Integration Broker gateway authenticates the basic-auth credentials and authorizes access to the service operation before PsoftQL runs. When the gateway rejects a request you see a normal HTTP error status code (and typically no JSON body). The most common case:
POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic <wrong password>
Content-Type: application/json
{ "records": [ { "recordName": "PSROLEDEFN" } ] }
HTTP/1.1 401 Unauthorized
A 401 means PeopleSoft never saw your payload β the OPRID is unknown, the password is wrong, the account is locked, or the OPRID does not have access to the CHG_SWS_PSOFTQL service operation. Fix the credentials or the security setup; the body of your request is irrelevant.
Layer 2 β PsoftQL handler (validation / authorization / SQL)
Once authentication passes, requests reach the PsoftQL handler. Handler errors come back with HTTP 200 β the gateway considers the call successful β but the JSON body carries responseCode: 400 and a human-readable errorMessages string. Always check responseCode and errorMessages, not the HTTP status, when calling PsoftQL.
Example: record not whitelisted
POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json
{
"rowLimit": 5,
"records": [ { "recordName": "PS_SOME_RANDOM_TABLE_XYZ" } ]
}
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {},
"responseCode": 400,
"errorMessages": "Error - At least one record in your request is not whitelisted or is not a real record name.",
"pageNumber": 1,
"productVersion": "2026-04-20"
}
The same message is returned whether the record genuinely does not exist or simply hasn’t been whitelisted for your permission list. This is intentional β the service does not reveal which case it is, so a caller cannot use error messages to enumerate the schema.
Example: malformed JSON in the request body
HTTP/1.1 200 OK
{
"data": {},
"responseCode": 400,
"errorMessages": "Error - Input JSON is invalid",
"pageNumber": 1,
"productVersion": "2026-04-20"
}
Validate your payload locally before sending it β the PsoftQL Validator page accepts JSON or XML and runs the official schema in-browser.
Example: pagination attempted in aggregate mode
POST /PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS_PSOFTQL HTTP/1.1
Authorization: Basic ...redacted...
Content-Type: application/json
{
"isAggregate": true,
"pageNumber": 2,
"records": [
{
"recordName": "PSROLEDEFN",
"aggregateConfig": {
"groupByFields": ["ROLETYPE"],
"aggregateFields": [
{ "function": "COUNT(1)", "outputLabel": "role_count" }
]
}
}
]
}
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {},
"responseCode": 400,
"errorMessages": "Error - Pagination is not supported in aggregate mode",
"isAggregate": true,
"totalGroups": 0,
"productVersion": "2026-04-20"
}
See Aggregate Queries below for the full list of features that do and do not work alongside isAggregate: true.
Silent behaviours that are not errors
Two responses look like errors but are not. Treat them as successful empty results, not failure modes:
| Symptom | What it means |
|---|---|
fields: [] with responseCode: 200 | Your filter matched zero rows. See the zero-row example in the Operator Examples page (Test 5). |
Unknown fieldName in criteriaFields is silently ignored | The handler currently does not validate that criteriaFields[].fieldName exists on the record. A typo in the field name produces a fully populated, unfiltered result. Always sanity-check that the returned rows actually match the criterion you intended. |
Error response cheat sheet
| HTTP | responseCode | Typical cause | What to fix |
|---|---|---|---|
| 401 | (no body) | Bad password, locked account, no service-op access | Verify credentials and that the OPRID is granted CHG_SWS_PSOFTQL |
| 200 | 400 | Record not whitelisted / does not exist | Add the record to the whitelist for your permission list |
| 200 | 400 | Malformed JSON | Validate locally with the PsoftQL Validator |
| 200 | 400 | Aggregate mode + pageNumber > 1 | Remove pageNumber, or drop isAggregate |
| 200 | 200, empty fields | No data matched filter | Not an error β handle as zero-result success |
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
pageNumberparameter 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 Example | Description |
|---|---|
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:
| Aspect | Standard Query | Aggregate Query |
|---|---|---|
objectType | "record" | "aggregateResult" |
| Data array | "fields" | "results" |
| Row identifier | rowNumber included | Not included |
| Pagination | pageNumber, nextPageNumber | Not present |
| Top-level indicator | None | "isAggregate": true |
| Count | None | "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 aggregationincludeAllDescriptions- 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
See Also
Use the PsoftQL Validator to check your PsoftQL syntax interactively.
includeFieldTypes,includeAllFieldLabels,includeKeyFieldIndicators- These are ignored in aggregate mode
4.3 - PsoftQL Validator/Builder
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.
4.4 - Campus Solutions PsoftQL - Examples
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. Setup-level examples (Institution, Subject, XLAT, etc.) below include a response sample so you can see the field shape before you wire one up. The parameterized examples (Get Person, Class Schedule, Service Indicators) are intentionally request-only β they depend on tenant-specific {{emplid}}, {{institution}}, and {{strm}} values you’ll substitute at integration time.
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"
}
]
}
Sample response β ACAD_ORG_TBL is effective-dated, so by default each row is the currently-effective version per ACAD_ORG. First two rows shown:
{
"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"
},
{
"rowNumber": 2,
"ACAD_ORG": "ACAD DIV",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Academic Division",
"DESCRSHORT": "Acad Div",
"INSTITUTION": "PSCCS"
}
]
}
},
"responseCode": 200,
"nextPageNumber": 2
}
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"
}
]
}
Sample response β SUBJECT is the key, ACAD_ORG links each subject to the owning academic org. First two rows:
{
"data": {
"SUBJECT_TBL": {
"objectType": "record",
"objectName": "SUBJECT_TBL",
"fields": [
{
"rowNumber": 1,
"INSTITUTION": "GLAKE",
"SUBJECT": "ACCOUNT",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Accounting",
"ACAD_ORG": "ACCOUNTING",
"CIP_CODE": "52.03"
},
{
"rowNumber": 2,
"INSTITUTION": "GLAKE",
"SUBJECT": "ARTHIST",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Art & Art History",
"ACAD_ORG": "ARTHISTORY",
"CIP_CODE": "50.07"
}
]
}
},
"responseCode": 200,
"nextPageNumber": 2
}
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'"
}
]
}
Sample response β PSXLATITEM is the translate-values table; XLATLONGNAME is the human-readable label that decodes a code like CLN β Clinical or DIS β Discussion:
{
"data": {
"PSXLATITEM": {
"objectType": "record",
"objectName": "PSXLATITEM",
"fields": [
{
"rowNumber": 1,
"FIELDNAME": "SSR_COMPONENT",
"FIELDVALUE": "CLN",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Clinical",
"XLATSHORTNAME": "Clinical"
},
{
"rowNumber": 2,
"FIELDNAME": "SSR_COMPONENT",
"FIELDVALUE": "CON",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Continuance",
"XLATSHORTNAME": "Continuanc"
},
{
"rowNumber": 3,
"FIELDNAME": "SSR_COMPONENT",
"FIELDVALUE": "DIS",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Discussion",
"XLATSHORTNAME": "Discussion"
}
]
}
},
"responseCode": 200,
"nextPageNumber": 2
}
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.5 - PsoftQL Operators & Filtering Examples
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.
These examples assume the target records are whitelisted for the calling service β see the PsoftQL Web Service documentation for configuration.
Supported Operators
| Operator | Meaning | Notes |
|---|---|---|
= | Equal | Default operator if omitted |
< | Less than | String or numeric comparison, depends on field type |
> | Greater than | |
<= | Less than or equal | |
>= | Greater than or equal | |
<> | Not equal | Equivalent to != |
!= | Not equal (alternate syntax) | Equivalent to <> |
LIKE | Pattern match | % matches any substring, _ matches exactly one character |
IN | Membership | Listed in schema, not currently honored β see note below |
Multiple entries in the criteriaFields array are joined with AND. For OR logic, move the condition into sqlWhereClause.
The IN operator appears in the JSON schema’s example list but the server currently does not produce a matching WHERE β¦ IN (β¦) clause β every attempted form (comma-separated string in fieldValue, parenthesized list, or a sibling fieldValues array) silently returns zero rows with a 200/responseCode: 200. Until this gap is closed, express membership filters with sqlWhereClause, for example:
{
"records": [
{
"recordName": "PSROLEDEFN",
"sqlWhereClause": "ROLENAME IN ('SA Administrator','SAIP Administrator','ACM Administrator')"
}
]
}
This route uses the raw SQL IN operator and works today. The PsoftQL-native IN operator is tracked on the road map.
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
Trimmed to two rows for readability. Both rows satisfy LASTUPDDTTM > '2020-01-01'; the presence of nextPageNumber: 2 confirms there is more data to page through.
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": [
{
"rowNumber": 1,
"ROLENAME": "ACM Administrator",
"VERSION": 1,
"ROLETYPE": "U",
"DESCR": "ACM Administrator",
"ROLESTATUS": "A",
"DESCRLONG": "",
"ALLOWNOTIFY": "Y",
"ALLOWLOOKUP": "Y",
"LASTUPDDTTM": "2021-05-19-07.38.09.805472",
"LASTUPDOPRID": "PPLSOFT"
},
{
"rowNumber": 2,
"ROLENAME": "ADS Designer",
"VERSION": 1,
"ROLETYPE": "U",
"DESCR": "ADS Designer",
"ROLESTATUS": "A",
"DESCRLONG": "Role for Application Dataset Designer",
"ALLOWNOTIFY": "Y",
"ALLOWLOOKUP": "Y",
"LASTUPDDTTM": "2021-05-19-07.38.09.805512",
"LASTUPDOPRID": "PPLSOFT"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"meta": {
"toolsVer": "8.61.03",
"dbname": "CS92DEV",
"dbType": "ORACLE",
"serverTimeZone": "PST",
"debugMessages": ""
},
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
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
Both FIELDNAME values sort alphabetically before "D". Because noEffectiveDateLogic and noEffectiveStatusLogic are both true, you’ll see EFFDT and EFF_STATUS fields in the raw output rather than them being filtered behind the scenes.
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSXLATITEM": {
"objectType": "record",
"objectName": "PSXLATITEM",
"fields": [
{
"rowNumber": 1,
"FIELDNAME": "AAP_PLAN_TYPE",
"FIELDVALUE": "E",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Establishment Level Plan",
"XLATSHORTNAME": "Estab",
"LASTUPDDTTM": "2001-09-14-10.23.47.000000",
"LASTUPDOPRID": "PPLSOFT",
"SYNCID": 2
},
{
"rowNumber": 2,
"FIELDNAME": "AAP_PLAN_TYPE",
"FIELDVALUE": "J",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Job Group Level Plan",
"XLATSHORTNAME": "Job Group",
"LASTUPDDTTM": "2001-09-14-10.23.29.000000",
"LASTUPDOPRID": "PPLSOFT",
"SYNCID": 3
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"meta": {
"toolsVer": "8.61.03",
"dbname": "CS92DEV",
"dbType": "ORACLE",
"serverTimeZone": "PST",
"debugMessages": ""
},
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
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 β Test 3 (>=)
The first two rows returned. Both have CRSE_ID >= '000100'. Each row in the real response carries the full record field set; non-essential fields are elided here with ... for readability.
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"CRSE_CATALOG": {
"objectType": "record",
"objectName": "CRSE_CATALOG",
"fields": [
{
"rowNumber": 1,
"CRSE_ID": "000100",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Social Work Practice 2B",
"SSR_COMPONENT": "LEC",
"GRADING_BASIS": "GRD",
"UNITS_MINIMUM": 15,
"UNITS_MAXIMUM": 15
},
{
"rowNumber": 2,
"CRSE_ID": "000101",
"EFFDT": "2009-01-01",
"EFF_STATUS": "A",
"DESCR": "Chemistry 1",
"SSR_COMPONENT": "LEC",
"GRADING_BASIS": "GRD",
"UNITS_MINIMUM": 15,
"UNITS_MAXIMUM": 15
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
Response β Test 4 (<=)
Same record, opposite end. Both rows have CRSE_ID <= '000010':
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"CRSE_CATALOG": {
"objectType": "record",
"objectName": "CRSE_CATALOG",
"fields": [
{
"rowNumber": 1,
"CRSE_ID": "000001",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Basic Musical Techniques"
},
{
"rowNumber": 2,
"CRSE_ID": "000002",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"DESCR": "Musicianship and Materials"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
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 β Test 5 (<>)
On the demo database every role is active, so filtering for ROLESTATUS <> 'A' returns zero rows. The response is still a successful 200 β an empty fields array is the normal “no matches” shape, not an error:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": []
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"productVersion": "2026-04-20"
}
Two things worth noting in zero-row responses:
nextPageNumberis absent β the same indicator you use to detect “last page” also signals “no data at all.”errorMessagesis still empty. An empty result and an error are distinct outcomes.
Response β Test 6 (!=)
ROLESTATUS != 'I' returns every role whose status is not Inactive β i.e. all active roles. First two rows shown:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": [
{
"rowNumber": 1,
"ROLENAME": "ACM Administrator",
"ROLETYPE": "U",
"DESCR": "ACM Administrator",
"ROLESTATUS": "A",
"LASTUPDDTTM": "2021-05-19-07.38.09.805472",
"LASTUPDOPRID": "PPLSOFT"
},
{
"rowNumber": 2,
"ROLENAME": "ADS Designer",
"ROLETYPE": "U",
"DESCR": "ADS Designer",
"ROLESTATUS": "A",
"LASTUPDDTTM": "2021-05-19-07.38.09.805512",
"LASTUPDOPRID": "PPLSOFT"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
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.
Always use excludeFields to drop OPERPSWD, PTOPERPSWDV2, OPERPSWDSALT, and ENCRYPTED when querying PSOPRDEFN. These fields contain hashed password material and should never leave the database.
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
Every row satisfies both conditions: OPRID starts with A and ACCTLOCK = 1. Note that OPERPSWD, PTOPERPSWDV2, OPERPSWDSALT, and ENCRYPTED are absent from each row β excludeFields filtered them out before serialization, so they were never on the wire:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSOPRDEFN": {
"objectType": "record",
"objectName": "PSOPRDEFN",
"fields": [
{
"rowNumber": 1,
"OPRID": "AADAMS",
"OPRDEFNDESC": "Andrew Adams",
"EMPLID": "PU310",
"EMAILID": "",
"OPRCLASS": "HCPPALL",
"ROWSECCLASS": "HCDPALL",
"ACCTLOCK": 1,
"FAILEDLOGINS": 0,
"LASTPSWDCHANGE": "2024-05-23",
"LASTUPDDTTM": "2021-09-08-05.01.17.440969",
"LASTUPDOPRID": "PPLSOFT"
},
{
"rowNumber": 2,
"OPRID": "ACARR",
"OPRDEFNDESC": "Alan Carr",
"EMPLID": "KU0150",
"EMAILID": "",
"OPRCLASS": "HCCPCSSA1010",
"ROWSECCLASS": "HCCPCSSA1010",
"ACCTLOCK": 1,
"FAILEDLOGINS": 0,
"LASTPSWDCHANGE": "2024-05-23",
"LASTUPDDTTM": "2021-09-08-05.01.17.440969",
"LASTUPDOPRID": "PPLSOFT"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
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 β Test 9 (%ADMIN)
Every returned ROLENAME ends in ADMIN:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": [
{
"rowNumber": 1,
"ROLENAME": "CHG_SWS_ADMIN",
"ROLETYPE": "U",
"DESCR": "SWS Admin User",
"ROLESTATUS": "A",
"DESCRLONG": "Access to setup SWS Configurations\r\n\r\nSee Cedar Hills Group, INC. SWS Documentation.",
"LASTUPDDTTM": "2024-08-01-01.22.35.757884",
"LASTUPDOPRID": "PS"
},
{
"rowNumber": 2,
"ROLENAME": "EOFD_ADMIN",
"ROLETYPE": "U",
"DESCR": "Fluid Discussions Admin",
"ROLESTATUS": "A",
"LASTUPDDTTM": "2021-05-19-07.38.10.000000",
"LASTUPDOPRID": "PPLSOFT"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
Response β Test 10 (%USER%)
Every returned ROLENAME contains USER somewhere β leading text, embedded, or trailing:
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSROLEDEFN": {
"objectType": "record",
"objectName": "PSROLEDEFN",
"fields": [
{
"rowNumber": 1,
"ROLENAME": "CHG_PSOFT_LENS_API_USER",
"ROLETYPE": "U",
"DESCR": "API User",
"ROLESTATUS": "A",
"LASTUPDDTTM": "2026-01-18-16.56.00.000000",
"LASTUPDOPRID": "PS"
},
{
"rowNumber": 2,
"ROLENAME": "CHG_SWS_USER",
"ROLETYPE": "U",
"DESCR": "SWS User",
"ROLESTATUS": "A",
"LASTUPDDTTM": "2024-08-01-01.22.35.757884",
"LASTUPDOPRID": "PS"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
Response β Test 11 (_lient)
The _ matches exactly one character. Here both matches are the literal XLAT value "Client" (_ matched the C):
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"PSXLATITEM": {
"objectType": "record",
"objectName": "PSXLATITEM",
"fields": [
{
"rowNumber": 1,
"FIELDNAME": "PTSF_NODE_TYPE",
"FIELDVALUE": "2",
"EFFDT": "1900-01-01",
"EFF_STATUS": "A",
"XLATLONGNAME": "Client",
"XLATSHORTNAME": "Client"
},
{
"rowNumber": 2,
"FIELDNAME": "RUNLOCATION",
"FIELDVALUE": "1",
"EFFDT": "1900-01-01",
"EFF_STATUS": "I",
"XLATLONGNAME": "Client",
"XLATSHORTNAME": "Client"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"productVersion": "2026-04-20"
}
If your database has no 6-letter strings ending in lient, the same request returns "fields": [] and no nextPageNumber β a successful empty result, identical in shape to the zero-row example shown for Test 5 above.
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": "="
}
]
}
]
}
Response
Every row satisfies both INSTITUTION = 'PSUNV' (from criteriaFields) and ACAD_CAREER = 'CNED' (from sqlWhereClause):
HTTP/1.1 200 OK
content-type: application/json; encoding=UTF-8
{
"data": {
"TERM_TBL": {
"objectType": "record",
"objectName": "TERM_TBL",
"fields": [
{
"rowNumber": 1,
"INSTITUTION": "PSUNV",
"ACAD_CAREER": "CNED",
"STRM": "0290",
"DESCR": "1997 Fall",
"TERM_BEGIN_DT": "1997-08-27",
"TERM_END_DT": "1997-12-15",
"SESSION_CODE": "1",
"WEEKS_OF_INSTRUCT": 15,
"TERM_CATEGORY": "R",
"ACAD_YEAR": "1997"
},
{
"rowNumber": 2,
"INSTITUTION": "PSUNV",
"ACAD_CAREER": "CNED",
"STRM": "0292",
"DESCR": "1997 Fall Qtr",
"TERM_BEGIN_DT": "1997-09-01",
"TERM_END_DT": "1997-11-30",
"SESSION_CODE": "1",
"WEEKS_OF_INSTRUCT": 10,
"TERM_CATEGORY": "R",
"ACAD_YEAR": "1997"
}
]
}
},
"responseCode": 200,
"errorMessages": "",
"pageNumber": 1,
"nextPageNumber": 2,
"productVersion": "2026-04-20"
}
The PsoftQL engine builds a single SQL WHERE that ANDs the two conditions together. There is no precedence ambiguity: criteriaFields entries and sqlWhereClause always join with AND.
Where to go next
- PsoftQL Syntax β the full reference for every request field
- Aggregate SQL Examples β filtering combined with
isAggregate: true - Campus Solutions Examples β filtering applied to real Campus records
4.6 - PeopleTools PsoftQL - Examples
In this section we will show detailed PsoftQL examples of how to extract PeopleTools information using SWS PsoftQL Syntax. Each example includes a sample response so you can see the field shape before deciding which to wire up.
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"]
}
]
}
Sample response β one user with their nested role list. LANGUAGE_CD_description appears because LANGUAGE_CD was listed in includeDescriptionsFor; CURRENCY_CD_description would appear too if the field had a non-blank value. The child PSROLEUSER.fields array carries each role assignment for the parent OPRID, joined via OPRID = ROLEUSER:
{
"data": {
"PSOPRDEFN": {
"objectType": "record",
"objectName": "PSOPRDEFN",
"fields": [
{
"rowNumber": 1,
"OPRID": "AADAMS",
"OPRDEFNDESC": "Andrew Adams",
"LANGUAGE_CD": "ENG",
"LANGUAGE_CD_description": "English",
"CURRENCY_CD": "",
"LASTUPDDTTM": "2021-09-08-05.01.17.440969",
"LASTUPDOPRID": "PPLSOFT",
"PSROLEUSER": {
"objectType": "record",
"objectName": "PSROLEUSER",
"fields": [
{ "ROLENAME": "CS - Student", "DYNAMIC_SW": "N" },
{ "ROLENAME": "CS - Student Applicant", "DYNAMIC_SW": "N" },
{ "ROLENAME": "EOPP_USER", "DYNAMIC_SW": "N" },
{ "ROLENAME": "PeopleSoft User", "DYNAMIC_SW": "N" },
{ "ROLENAME": "Standard Query Permissions", "DYNAMIC_SW": "N" }
]
}
}
]
}
},
"responseCode": 200,
"nextPageNumber": 2
}
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"
}
]
}
Sample response (heavily trimmed β one parent record, one child field, one grand-child field). The full response for two records is ~60 KB because every PSRECDEFN explodes into dozens of PSRECFIELDDB rows, each with its own PSDBFIELD child. Filter aggressively with criteriaFields on the parent (RECNAME = 'YOUR_RECORD') when you only need one record’s structure:
{
"data": {
"PSRECDEFN": {
"objectType": "record",
"objectName": "PSRECDEFN",
"fields": [
{
"rowNumber": 1,
"RECNAME": "PSROLEDEFN",
"RECTYPE": 0,
"PSRECFIELDDB": {
"objectType": "record",
"objectName": "PSRECFIELDDB",
"fields": [
{
"RECNAME": "PSROLEDEFN",
"FIELDNAME": "ALLOWLOOKUP",
"FIELDNUM": 17,
"DEFFIELDNAME": "Y",
"USEEDIT": 8396800,
"PSDBFIELD": {
"objectType": "record",
"objectName": "PSDBFIELD",
"fields": [
{
"FIELDNAME": "ALLOWLOOKUP",
"FIELDTYPE": 0,
"LENGTH": 1,
"OBJECTOWNERID": "PPT",
"DESCRLONG": "Allow Recipient Lookup"
}
]
}
}
]
}
}
]
}
},
"responseCode": 200
}
This three-level nest is a useful pattern for “schema discovery” integrations β building a client-side model of every field on a record, including its underlying type from PSDBFIELD.
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"
}
]
}
]
}
Sample response (top 3) β note the timestamps step backward as you walk the array, confirming the DESC order is applied. This pattern is the standard “audit/recent-changes” query for any record that carries LASTUPDDTTM:
{
"data": {
"PSPROJECTDEFN": {
"objectType": "record",
"objectName": "PSPROJECTDEFN",
"fields": [
{ "rowNumber": 1, "PROJECTNAME": "CHG_CI_TEST", "LASTUPDDTTM": "2026-05-08-23.22.42.036849" },
{ "rowNumber": 2, "PROJECTNAME": "CMALEK_NODE", "LASTUPDDTTM": "2026-04-23-02.15.14.806879" },
{ "rowNumber": 3, "PROJECTNAME": "CHG_TOKEN_GEN_POC", "LASTUPDDTTM": "2026-04-22-16.53.29.852062" }
]
}
},
"responseCode": 200,
"nextPageNumber": 2
}
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.7 - Aggregate SQL Translation Examples
This document shows how PsoftQL aggregate requests translate to SQL queries β and what comes back from the wire. It is both an internal reference for implementation and a guide for callers assembling aggregate requests. Each example below shows three things: the PsoftQL request, the SQL it generates, and the actual JSON response captured against a live PeopleSoft Campus Solutions 9.2 / PeopleTools 8.61 environment. For the response shape in the abstract β objectType: "aggregateResult", results array instead of fields, the top-level isAggregate and totalGroups markers β see Aggregate Response Structure.
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
Response
{
"data": {
"PSROLEDEFN": {
"objectType": "aggregateResult",
"objectName": "PSROLEDEFN",
"groupByFields": ["ROLETYPE"],
"aggregateFields": ["role_count"],
"results": [
{ "ROLETYPE": "Q", "role_count": 14 },
{ "ROLETYPE": "U", "role_count": 252 }
]
}
},
"responseCode": 200,
"errorMessages": "",
"isAggregate": true,
"totalGroups": 2,
"productVersion": "2026-04-20"
}
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
Response
On this demo database every role is active, so the counts match Example 1 β but adding the filter is what guarantees that, in a database with both Active and Inactive roles, you’d only see the active ones in the result:
{
"data": {
"PSROLEDEFN": {
"objectType": "aggregateResult",
"objectName": "PSROLEDEFN",
"groupByFields": ["ROLETYPE"],
"aggregateFields": ["active_role_count"],
"results": [
{ "ROLETYPE": "Q", "active_role_count": 14 },
{ "ROLETYPE": "U", "active_role_count": 252 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 2
}
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
Response
Each row in results represents one unique combination of the GROUP BY fields. With two groupings of size 2 there are up to 4 possible buckets β the demo database happens to have data in 2 of them:
{
"data": {
"PSROLEDEFN": {
"objectType": "aggregateResult",
"objectName": "PSROLEDEFN",
"groupByFields": ["ROLETYPE", "ROLESTATUS"],
"aggregateFields": ["role_count"],
"results": [
{ "ROLETYPE": "Q", "ROLESTATUS": "A", "role_count": 14 },
{ "ROLETYPE": "U", "ROLESTATUS": "A", "role_count": 252 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 2
}
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
Response (first 4 of many groups, trimmed for readability via rowLimit: 5)
All five aggregate functions are evaluated for each group. Numeric fields come back as JSON numbers (not strings); preserve precision in your client by parsing into a decimal type rather than a 64-bit float when the values can exceed safe-integer range β note max_salary of 192132000 in row 2:
{
"data": {
"JOB": {
"objectType": "aggregateResult",
"objectName": "JOB",
"groupByFields": ["DEPTID"],
"aggregateFields": ["employee_count", "total_salary", "avg_salary", "min_salary", "max_salary"],
"results": [
{
"DEPTID": "1000",
"employee_count": 7,
"total_salary": 2155000,
"avg_salary": 307857.1428571429,
"min_salary": 180000,
"max_salary": 490000
},
{
"DEPTID": "10000",
"employee_count": 827,
"total_salary": 602910821.362,
"avg_salary": 729033.6413083434,
"min_salary": 0,
"max_salary": 192132000
},
{
"DEPTID": "10001",
"employee_count": 11,
"total_salary": 183759.835,
"avg_salary": 16705.43954545455,
"min_salary": 1920,
"max_salary": 36813
}
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 5
}
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.
Response
When there is no grouping the results array always has exactly one element. totalGroups is 1 even though there’s no real grouping happening β the single global aggregate is treated as one degenerate group:
{
"data": {
"PSROLEDEFN": {
"objectType": "aggregateResult",
"objectName": "PSROLEDEFN",
"groupByFields": [],
"aggregateFields": ["total_roles"],
"results": [
{ "total_roles": 266 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 1
}
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
Response (first 3 groups via rowLimit: 3)
Note unique_students (DISTINCT EMPLID) versus total_enrollments (raw COUNT(1)) β the gap tells you how many students were enrolled in more than one class for that term:
{
"data": {
"STDNT_ENRL": {
"objectType": "aggregateResult",
"objectName": "STDNT_ENRL",
"groupByFields": ["INSTITUTION", "STRM"],
"aggregateFields": ["unique_students", "total_enrollments"],
"results": [
{ "INSTITUTION": "GLAKE", "STRM": "0370", "unique_students": 1, "total_enrollments": 2 },
{ "INSTITUTION": "GLAKE", "STRM": "0410", "unique_students": 1, "total_enrollments": 1 },
{ "INSTITUTION": "GLAKE", "STRM": "0430", "unique_students": 1, "total_enrollments": 2 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 3
}
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
Response (first 4 of 12 institutions)
The correlated sub-select restricts each row to its current effective-dated version before grouping, so org_count is the count of currently effective org rows per institution β not the count of every historical version. Set noEffectiveDateLogic: true to count every historical row instead.
{
"data": {
"ACAD_ORG_TBL": {
"objectType": "aggregateResult",
"objectName": "ACAD_ORG_TBL",
"groupByFields": ["INSTITUTION"],
"aggregateFields": ["org_count"],
"results": [
{ "INSTITUTION": "GLAKE", "org_count": 18 },
{ "INSTITUTION": "PSAUS", "org_count": 25 },
{ "INSTITUTION": "PSCCS", "org_count": 37 },
{ "INSTITUTION": "PSESP", "org_count": 26 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 12
}
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
Response
Same shape as Example 4 but capped at 10 groups. The response’s totalGroups reflects the truncated count, not the underlying total β so for true “top N” queries you typically add an explicit ORDER BY (via orderByFields) on the aggregated column, otherwise rowLimit just trims the first N alphabetical DEPTID buckets.
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
Response
The MAX(LASTUPDDTTM) aggregate returns the most recent edit timestamp for each group as a string in PeopleSoft’s standard format. Mixing a numeric aggregate (COUNT) with a non-numeric one (MAX on a datetime) in the same request is fine β each column is computed independently:
{
"data": {
"PSROLEDEFN": {
"objectType": "aggregateResult",
"objectName": "PSROLEDEFN",
"groupByFields": ["ROLETYPE"],
"aggregateFields": ["role_count", "last_updated"],
"results": [
{ "ROLETYPE": "U", "role_count": 2, "last_updated": "2007-07-11-16.45.39.000000" }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 1
}
Example 10: Users per Role (Count from Related Table)
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
Response (first 5 of many groups via rowLimit: 5)
{
"data": {
"PSROLEUSER": {
"objectType": "aggregateResult",
"objectName": "PSROLEUSER",
"groupByFields": ["ROLENAME"],
"aggregateFields": ["user_count"],
"results": [
{ "ROLENAME": "ACM Administrator", "user_count": 1 },
{ "ROLENAME": "AG Composer Administrator", "user_count": 1 },
{ "ROLENAME": "AG Composer User", "user_count": 1 },
{ "ROLENAME": "AWE Administrator", "user_count": 2 },
{ "ROLENAME": "AppServer Administrator", "user_count": 4 }
]
}
},
"responseCode": 200,
"isAggregate": true,
"totalGroups": 5
}
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:
| Input | Output |
|---|---|
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
- If
isAggregate: trueandpageNumber > 1β Error - If
isAggregate: trueand multiple records withparentRecordNameβ Error - If
aggregateConfigmissing on the record whenisAggregate: trueβ Error - If
aggregateFieldsempty whenisAggregate: trueβ Error - Validate field names in
groupByFieldsexist on the record - Parse and validate field names in
functionexpressions exist on the record (except*and literals like1)
5 - 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
6.1 - Why was SWS Created?
The genesis story for SWS now lives on the About page β the natural home for company and product backstory.
This page is preserved as a redirect so existing links continue to resolve.
6.2 - Terminology
See our Integration Book - Terminology Sections for a deeper Integration Broker vocabulary; this page covers just the terms that come up repeatedly in SWS documentation.
HTTP / REST Terms
- 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
curlorwget. 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
PeopleSoft Concepts
These terms come from PeopleSoft itself. SWS uses them constantly. If you have a PeopleSoft background you can skip this section; if you’re integrating with SWS from a non-PeopleSoft perspective, the definitions below will save you a lot of context-switching.
- Integration Broker (IB)
- PeopleSoft’s built-in middleware for inbound and outbound web services. Everything SWS does runs through Integration Broker: the
RESTListeningConnectorreceives the HTTP request, IB authenticates the caller, routes to the service operation, and serializes the response. The companion book Integration Broker β The Missing Manual covers IB in depth. - PSIGW
- PeopleSoft Integration Gateway β the Java web application (a servlet inside PSIGW.war) deployed to the PeopleSoft web server tier. The gateway is the network edge of IB: it accepts inbound HTTP and forwards messages to the application server. Inbound REST calls hit a path like
/PSIGW/RESTListeningConnector/<NODE>/<SERVICE>/.... - Service
- An IB grouping for related service operations.
CHG_SWSis the Service name used by SWS β it appears in the request URL as the prefix segment after the listening node. - Service Operation
- The specific handler within a Service that runs in response to an inbound message. SWS ships two:
CHG_SWS_GET(called by every admin-configured SWS path) andCHG_SWS_PSOFTQL(the advanced caller-driven endpoint). When the docs say “configure security on the service operation,” they mean granting access to one of these two names in Security β Permissions & Roles. See also the Service vs Service Operation alert on the configuration page. - OPRID
- The PeopleSoft user identifier β what other systems call a username. Stored in
PSOPRDEFN. SWS authenticates inbound REST calls against an OPRID + password using HTTP Basic Authentication. Perβuser data filtering inside SQL relies on%OPERATOR(the current OPRID) and%EMPLOYEEID(the EMPLID the OPRID is associated with). - PSOPRDEFN
- The PeopleSoft table that stores user accounts (OPRIDs), passwords, locked-account flags, and the link from each OPRID to its primary role. Whenever you read “OPRID and password from PSOPRDEFN,” that’s the table being checked.
- Role
- A named bundle of permission lists assigned to one or more OPRIDs. PeopleSoft security flows OPRID β role β permission list β access. SWS itself does not check roles directly; it checks the permission lists granted through the OPRID’s roles.
- Permission List (CLASSID)
- The leaf of the PeopleSoft security tree β the unit that actually grants access to service operations, components, and (for SWS) configured URL paths. Stored with column name
CLASSIDin the database, hence the column name in the SWS whitelist tableC_SWS_REC_WL. The CLASSID is not the same string as the OPRID or the role; look it up directly in Security β Permission Lists rather than guessing. - Component
- The PeopleSoft equivalent of a “page” or “form.” Configuration pages SWS delivers (for example
CHG_SWS_CONF_TBLfor the SWS setup grid) are components. You can grant or revoke a permission list’s access to a component to control who sees the SWS admin UI. - Record
- PeopleSoft’s metadata wrapper around a database table or view β it carries the field list, field types, key definitions, prompt table references, and other attributes the application uses. SWS reads record metadata at run time to know which fields exist on a table, which fields are keys, and which fields have XLAT or prompt-table descriptions. PsoftQL requests use the record name (e.g.
PSROLEDEFN), not the underlying database table name (e.g.PS_PSROLEDEFN). - XLAT
- Translate values β PeopleSoft’s small-domain lookup mechanism for single-character or short codes that decode to human-readable labels (e.g.
ROLESTATUS = 'A'β"Active",ROLESTATUS = 'I'β"Inactive"). Stored centrally inPSXLATITEM, keyed by field name and value. SWS can inject the decoded label into responses viaincludeAllDescriptionsorincludeDescriptionsForon a PsoftQL request. - Prompt Table
- An XLAT-style decode for codes whose value-set is large or business-specific (e.g.
DEPTIDβ department description). Instead of central rows inPSXLATITEM, each prompt table is a separate record (e.g.DEPT_TBLdecodesDEPTID). SWS resolves prompt-table descriptions the same way as XLATs when you ask for them withincludeDescriptionsFor. - EFFDT
- “Effective date” β a key field on PeopleSoft records that hold time-versioned data (employees, courses, configurations). Each row’s
EFFDTis the date that row’s data first takes effect; the row remains in effect until a newer row with a laterEFFDTarrives for the same key. SWS auto-resolves to the row that is current “today” unless you supplynoEffectiveDateLogicoreffectiveDateOverride. - EFF_STATUS
- A character field on effective-dated records, paired with
EFFDT, that marks a row asActive orInactive. SWS auto-filters toEFF_STATUS = 'A'unless you setnoEffectiveStatusLogic: true. Inactive rows exist when an organization needs to retire anEFFDTrow but keep the history. - EFFSEQ
- “Effective sequence” β a third key on a small number of records (notably
JOB) that lets multiple rows share the sameEFFDTfor the same entity, ordered by sequence number. SWS auto-resolves to the highestEFFSEQfor the chosenEFFDTunlessnoEffectiveSequenceLogic: trueis set. - Meta-SQL
- PeopleSoft’s database-portable SQL macro layer β text inside SQL of the form
%MACRO,%TABLE(record),%CURRENTDATEIN,%DATEIN(date), etc., that the PeopleSoft runtime expands into the correct dialect for Oracle or SQL Server before execution. SWS SQL configurations and PsoftQLsqlWhereClausestrings both pass through this expansion.%TABLE(PSOPRDEFN)resolves to the physical table name (e.g.PS_PSOPRDEFNorPSOPRDEFN) at run time so your configuration survives Oracle’s release-to-release table renames. - Whitelist (SWS)
- SWS-specific concept implemented in the
C_SWS_REC_WLtable. Each row pairs aCLASSID(permission list) with aRECNAME(record), declaring that callers with that permission list may include that record in aCHG_SWS_PSOFTQLrequest. Whitelist filtering applies only toCHG_SWS_PSOFTQL;CHG_SWS_GETcontrols access at the URL-path level instead (via the permission-list grid on each SWS configuration row). See Data Access Models.
6.3 - SWS Assumptions and Limitations
Assumptions
- You have a working PeopleSoft Integration Broker (IB) setup, including a configured
RESTListeningConnectoron PSIGW. - You want to pull information out of PeopleSoft. SWS is read-only by design.
- PeopleTools version greater than 8.56.
- You can create custom Oracle/SQL Server objects in your PeopleSoft database (SWS ships custom records, application packages, and a service operation).
What SWS does not do
These are intentional product boundaries, not bugs. If your use case is on this list, SWS is the wrong tool and another approach is needed.
Read-only, GET-only
SWS exposes PeopleSoft data as HTTP GETs. It does not create, update, or delete PeopleSoft data. There is no PUT, POST-to-write, or DELETE pathway. To push data into PeopleSoft you still need a traditional Integration Broker service operation, a Component Interface, or a delivered web service. Generic insert/update support is discussed on the road map; it does not exist today.
PeopleSoft must be the server
SWS only works when PeopleSoft is the HTTP server answering inbound requests. If you need PeopleSoft to make an outbound call to a third-party REST API, use a standard Integration Broker outbound consumer β SWS plays no role there.
Data is returned in PeopleSoft shape
By default the response uses PeopleSoft field names (EMPLID, ACAD_CAREER, DESCR) and PeopleSoft formats (ISO dates, raw XLAT codes). Two partial mitigations exist today:
- SQL configurations can alias columns in the Output Fields Grid so the client sees friendly names (see the SQL worked example).
- PsoftQL can request decoded XLAT and prompt-table descriptions via
includeDescriptionsForandincludeAllDescriptions.
Neither covers true data transformation β flattening parent/child trees, computing derived values, or reshaping the response schema for a specific vendor. A future release may add a transformation pipeline; tracked on the road map.
CSV output is SQL-only
CSV (Accept: text/csv) only works for SQL-type SWS configurations. PsoftQL responses are JSON or XML β they are nested by design and CSV has no representation for the parent/child structure. See the Syntax Types comparison on the configuration page.
IN operator in criteriaFields is not honored
The JSON schema lists IN as a supported operator value, but the current server build silently returns zero rows for every input form (comma-separated string, parenthesized list, fieldValues array). Use sqlWhereClause with native SQL IN (...) instead. See the IN operator status note on the Operator Examples page. Closing this gap is on the road map.
Aggregate queries cannot paginate or join
When isAggregate: true is set on a PsoftQL request:
- Only a single root record is allowed β no child records (
parentRecordName) and no joins. - Pagination is not supported β
pageNumbermust be omitted or1. Setting it to2or higher returnsresponseCode: 400with the message “Pagination is not supported in aggregate mode.” - The flags
includeFieldTypes,includeAllFieldLabels, andincludeKeyFieldIndicatorsare ignored.
For the full list of what does and does not work alongside isAggregate: true, see the Aggregate Queries section of the PsoftQL syntax reference.
XML isAggregate mode
Aggregate queries in XML request format do not currently produce the same well-formed aggregate response that JSON requests do. Stick with JSON when using isAggregate: true.
Field-name typos in criteriaFields are silent
The handler does not validate that criteriaFields[].fieldName exists on the target record. A typo causes the criterion to be silently dropped and the request returns the unfiltered result. Always spot-check that the returned rows actually match the criterion you intended. See Silent behaviours on the syntax page.
Authentication options are limited
The only viable REST authentication mechanism today is HTTP Basic Authentication tied to a PeopleSoft OPRID / password stored in PSOPRDEFN. SWS does not currently support API keys, OAuth 2.0 client-credentials grants, mutual TLS, or JWT bearer tokens at the application layer. This is a PeopleSoft IB constraint, not an SWS choice β anything you read in the Integration Broker REST security reference also applies to SWS.
Caching is constrained by the IB cache
SWS exposes the PeopleTools RESTListeningConnector cache via the Minutes to Cache Response configuration field. Read the Caching article before enabling it β the cache key does not include the caller’s OPRID, so caching a response that uses %EMPLOYEEID or %OPERATOR will leak one caller’s data to the next.
Other known boundaries
- Attachment fields are not auto-handled. Records with
EFFDT/EFF_STATUS/EFFSEQare auto-handled; binary attachment fields (BLOB columns referenced byPV_ATT_*metadata) are not yet auto-rendered. - No
meta/endpointsdiscovery endpoint yet. There is no live “list every SWS-configured path” endpoint β the closest substitute today is querying the configuration tables (C_SWS_CONF_TBLand friends) directly. - OpenAPI / Swagger generation is on the road map but not yet shipped.
For features under active consideration, see the Road Map.
7 - Legal & Licensing
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.
The end user license agreement (EULA) that governs your use of SWS: scope, restrictions, audit rights, confidentiality of source, termination, and governing law.
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.
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.
Related
- Purchase SWS β License tiers and pricing.
- SWS Installation β Lists the delivered objects, including the
C_SWS_CONF_TBLrecord 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_TBLwhose 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:
- Redistribute, sell, rent, lease, sublicense, or otherwise transfer the SWS Software or any portion of it to any third party.
- 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.
- 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.
- Use the SWS Software to develop, market, or operate a product that competes with the SWS Software.
- Exceed the Web Service count authorized by Customer’s License Tier without truing up to the appropriate tier under Section 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:
- 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.
- 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.
- 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.
7. Copyright and Ownership
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:
- Unauthorized redistribution or disclosure of the SWS Software.
- Refusal to run, or failure to return the results of, the self-audit procedure under Section 6.
- Removal or obscuring of copyright notices required under Section 7.
- Non-payment of undisputed fees when due.
On termination, Customer shall:
- Immediately cease all use of the SWS Software.
- 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
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.
Cedar Hills Group does not connect to your PeopleSoft environment, install agents, or collect telemetry. You run the audit query yourself, in your own tool of choice (SQL Developer, PeopleSoft Query Viewer, SQLPlus, TOAD, DBeaver, or any direct database client). You are in full control of the data that leaves your environment.
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;
The column name for the Active flag in PS_C_SWS_CONF_TBL is shown here as ACTIVE. Depending on the installed release of SWS, the column may also appear as EFF_STATUS (with value 'A' for active) or under a CHG_-prefixed name. Before treating an audit result as final, confirm the exact column name against the record definition in your Application Designer or with DESCRIBE PS_C_SWS_CONF_TBL (Oracle) / sp_columns (SQL Server). Update the WHERE clause to match.
How to Submit Your Result
- Run the count query in every PeopleSoft environment that has SWS installed.
- Record the
DBNAMEof 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. - 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.
- Contact Cedar Hills Group. The contact details on the Purchase SWS page apply.
- Cedar Hills Group will issue a purchase order for the next-higher tier, prorated for the remainder of your current support term.
- Once the true-up purchase order is paid, your License Tier is upgraded retroactive to the date the overage began.
- 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 Statement
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:
- The SWS source files as installed in every PeopleSoft environment.
- Any copy of the SWS source files moved into version control, backup, archive, or migration tooling.
- 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.
What Copyright Protects β and Why It Matters to You
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:
- 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.
- 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.
Related
- License Agreement β the full contractual terms governing use of SWS.
- Self-Audit Procedure β how to confirm your licensed web service count each year.
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.
- Read the Start Page for an overview of SWS
- Follow the Your First Web Service tutorial to see SWS in action
- Review SWS Concepts to understand configuration and requests
See the Purchase Page for pricing tiers and licensing options.
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.
Why SWS Was Built
Innovation is often born out of frustration with the status quo. That was the case for Chris Malek, who had worked on numerous integration projects that required the same web services to be re-implemented over and over again. Each project had some similar features and others that were unique. 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. That’s where SWS comes in.
SWS was created to address the challenges of repetitive and unsustainable web services. It takes advantage of Chris’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. The result is a single configuration-driven product that turns weeks of bespoke development into minutes of configuration.
Cedar Hills Group, Inc. has several clients and system integrators using SWS in various releases. This public release is the culmination of 25 years of PeopleSoft integration experience packaged into a best-of-breed solution.
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.
$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.
$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.
$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.
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 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
- Campus Solutions
- Human Capital Management (HCM)
- Financials
- Compatible with PeopleTools 8.57+
- Standard Application Designer project installation
- Zero modifications to delivered PeopleSoft objects
- 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.