Simple Web Services - User Documentation

Introduction

Simple Web Services (SWS) is a PeopleSoft bolt-on that allows a power user to expose a SQL statement as a REST web service. This is set up via configuration. After you have installed the product, no additional code migrations are required to create or modify web services.

This document will guide the reader through all aspects of the SWS product.

Supported PeopleSoft versions

SWS has been tested on the following PeopleSoft releases:

  • 8.56
  • 8.57

SWS can run on any PeopleSoft application like Finance, HCM or Campus.

Getting Started

Before configuring a new web service there are a few things to think through. We will cover them here at a high-level then go into detail in the subsequent sections.

  • URL “Path” - A REST API is generally laid out with an intentionally designed structure that manifests in the URL Paths. In SWS, the URL path will be used to find the SQL statement to run. 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 requested. The URL paths for all SWS configured services will be global to the target database. A URL Path structure could be something like this:
    • {prefix}/person/{emplid}
      • This output information about a person.
    • {prefix}/person/{emplid}/phone
      • This would output phones for a person.
    • {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.
  • SQL - The SWS code looks up a configured SQL statement, applies SQL criteria from the request, executes the query, encodes the data, and returns it to the API client. So you need to plan the SQL statement carefully. The API client can send in parameters in the URL, query string, or headers that bind to SQL statement criteria. You need to think through what parameters you need to pass and how you want the client to pass them. We will have many examples of this later in this document.
  • Output field names - You can define the output field names that map to the SQL columns returned. These will be the field names in the output encoding. If you don’t provide output field names then the data is returned with generic “COLUMN1”, “COLUMN2”, “COLUMNx” naming convention. This will not be helpful to the client.
  • Security - Security is always very important with PeopleSoft data as the database holds very sensitive information. SWS is based on the PeopleSoft REST services. The only viable authentication mechanism for REST based services is “Simple 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.

Terminology

This section highlights some terminology used in this document.

Client
An HTTP user of your API. This could be a simple command line (CLI) application like curl or wget. It will likely be some external application that needs to pull data from PeopleSoft.
PSIGW
PeopleSoft Integration Gateway - This is the web server that is running your integration broker.
Encoding
The way the data is “wrapped” and presented to the client: JSON, XML or CSV.
Request
The HTTP request made by the client. This includes the URL path, query strings, and HTTP Headers.

Configuring a new web service

Configuring a new web service in SWS is easy and fast. You can deploy a new SQL statement as a brand 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.

  • Unique Identifier: This is a system generated GUID that will be generated at save time.
  • Description: This is the description of the configuration.
  • URL Path: This field is very important and will map to the full URL that a client will use to target your web service.
  • 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.
  • SQL Statement Text: This is the section where you enter the SQL statement that will run.
    • There is some special syntax for binding variables from the client request to the SQL statement. We will cover that detail in the next section.
  • Row Limit: Optional row limit. You can specific a non-zero number here and the handler will stop the output after that number of output rows has been encoded.
  • Default Encoding: Used to set a default encoding scheme if the client does not include one in the request. All SWS web services automatically handle JSON, CSV and XML encodings.
  • 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.
  • 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”.
  • 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 grid is where you name the SQL columns that show up in the encoding section. For JSON and XML encodings, 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.

Security Setup

For SWS, there are two main areas of security.

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

SWS Administrator Security

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

SWS Administrator Delivered Security:

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

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.

For API users that need to invoke the 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.

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

What is required to create an API User for SWS?

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

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

SWS API Delivered Security:

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

Example User ID and Basic Authentication Code

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

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

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

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

Security Best Practices

For the purpose of 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 makes password rotation near impossible since you have to coordinate with more than one group.
      • Shared accounts also makes auditing more difficult.
    • Having separate accounts makes it easy for you to shut off one system and not impact the others.
  • Each API User should have a complex password that is generated by a password manager.
    • Store your passwords in some sort of password database.
    • Do NOT email passwords to users. Emails persist basically 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 permission that are not shared across other users.
    • For our three internal system that may end up looking like this:
      • OPRID: Z_STELLAR_WIND_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_STELLER_WIND_API_USER
          • Permission List: Z_STELLER_WIND_API_USER
      • OPRID: Z_PRISM_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_PRISM_API_USER
          • Permission List: Z_PRISM_API_USER
      • OPRID: Z_MYSTIC_API_USER
        • ROLE: CHG_SWS_USER
          • Permission List: CHG_SWS_USER
        • ROLE: Z_MYSTIC_API_USER
          • Permission List: Z_MYSTIC_API_USER

If you want additional information on PeopleSoft REST security, I would recommend reading the REST Security IB Book Chapter

Invoking The Service

The basic HTTP method signature of SWS operations is based on the following template.

GET https://{{host}}/PSIGW/RESTListeningConnector/{{targetNode}}/CHG_SWS/{{SWSPathAndQueryString}} HTTP/1.1
Authorization: Basic {{authString}}
Accept: {{desiredEncoding}}
  • {{host}} - The host name of your Integration broker web server.
  • {{targetNode}} - The target node. This is generally the “default local node” for the target database.
  • {{SWSPathAndQueryString}} - The path and any query string. This matches the path you setup in SWS configuration.
  • {{authString}} - The base64 encoded OPRID and password.
  • {{desiredEncoding}} - The desired encoding scheme. All SWS configurations handle 3 encoding schemes automatically and with no additional setup or coding. If this header is not included then the default value configured on the SWS configuration will be used.
    • text/csv - Use this for CSV output
    • application/json - Use this for JSON output
    • application/xml - Use this for XML output

For each SWS setup, there is an “HTTP Info” tab that shows the HTTP and curl syntax to invoke the service operation. This code uses information in the PeopleTools tables to determine the URL for your integration broker. There are situations where this may not be correct for some infrastructures. This is highly dependent on your configuration of PeopleSoft and your network infrastructure.

Here is one example of the HTTP Info tab for a simple web service.

The HTTP Request:

GET http://psft-cs-856.c.peoplesoftdemo-1470578694381.internal:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/person/FA0010/ HTTP/1.1
Authorization: Basic Wl9URVNUX0FQSV9VU0VSOnByb2N0b3ItY29uc3VsYXItZXN0aGVyLWh1bGwtZmxvb2Q=
Accept: application/json

The HTTP Response:

HTTP/1.1 200 OK
Connection: close
Date: Mon, 30 Dec 2019 04:27:19 GMT
Content-Length: 104
Content-Type: application/json; encoding="UTF-8"
x-PeopleSoftRequestID: aa82d81c-2abc-11ea-bc3b-ededaff068a5
Content-Encoding: gzip
x-success: True
Set-Cookie: JSESSIONID=_-VVDuymKRE7UX8j3k-IqUeiDeR_6ziwyqTLNDhDSZUOfq3IxBHN!862629156; path=/; HttpOnly,psmap=; domain=.c.peoplesoftdemo-1470578694381.internal; path=/
{  "data": [
    { "full_name": "Joseph Aranda",
      "date_of_birth": "1980-02-18"
    }
  ]
}

Passing Parameters

In SWS, you can configure parameters that need to be passed by the client. These are substituted into the SQL statements as bind variables. Bind variables can some from 3 different locations:

  1. As part of the URL Path.
    • For example, you may request information on a certain EMPLID by using a path like /person/0222233/addresses where 0222233 is the EMPLID requested.
  2. In a query string variable
    • For example, you may ask for a certain address type by using a query string like /person/0222233/addresses?addressType=MAIL.
  3. In an custom HTTP Header. This is probably the least commonly used method. However, this may provide some benefits for older clients.
    • For example, you may ask for a country code in an X-Country1 header field. These parameters are defined in the SWS setup using the “parameters” grid.
  • Parameters can be setup with default values that are used if the client does not specify a value.
    • The default values are normally hard-coded values.
    • There is limited support for PeopleCode variables.
      • %OPERATORID - Current User
      • %EMPLOYEEID - Current User’s EMPLID.
      • %LOCALNODE - The databases current local node name.

Here is an example configuration that uses all 3 methods. If you look at the sections in the screenshot that are highlighted you can see how all the parameters are defined in the grid and URL Path. Then how they are used in the SQL statement.

The HTTP signature of this SWS setup is.

GET https://your-ib-host/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/person/{{emplid}}/addresses?addressType={{addressType}} HTTP/1.1
Authorization: Basic {{authString}}
X-Country1: {{value}}
Accept: application/json

To ask for EMPLID: 012334, Address Type: MAIL, and Country: USA the sec

GET https://your-ib-host/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/person/012334/addresses?addressType=MAIL HTTP/1.1
Authorization: Basic {{authString}}
X-Country1: USA
Accept: application/json
  • Max Parameter Length - This is used to set length restriction on the parameters to avoid SQL Injection.
  • Path parameters are automatically inserted into the grid when they are detected in the path component in this form {{variableName}}
  • Parameter String for SQL - This is a string in the form of {{variableName}}. This string is used in your SQL Where clause. Do NOT add quotes around the {{variableName}} in the SQL text. The framework will do that at run time.
  • If you are having trouble with parameters, it is recommenced that you turn on the “include metadata in response” parameter and the resolved SQL statement will be in the response. This may help you determine what the issue is.

Output Headers

SWS will always output several information HTTP Headers automatically in additional to what the Integration Broker sends. These can be useful to the client for determining success or for logging purposes.

HTTP Header ValuesDescription & Values
x-PeopleSoftRequestIDGUID of PeopleSoft IB transaction. This generated by the PeopleTools IB framework and is used in the logging.
x-successTrue - Successful API call
False - UnSuccessful API Call.
This is the main flag returned by SWS to represent success or failure. (see “HTTP Status Codes” section below)
Content-TypeContent type of the response. This will be either application/xml, application/json or text/csv

HTTP Status Codes

Once the SWS framework is configured most calls are going to return an HTTP Status 200 OK even when there is an error.

The PeopleTools REST framework that SWS is built on does not give complete control over HTTP status codes. Additionally, there are several bugs and design constraints in the Oracle framework that make fully supporting a REST model with “proper” HTTP status codes difficult. We have an extensive examples of these limitation in Controlling HTTP Status Codes chapter in our IB Book. So for the SWS product, we decided to return the x-success HTTP status code as the signal to the client if the call was successful. This is not ideal but it works within the boundaries given by the PeopleTools framework.

Metadata

All SWS configuration have the option of sending metadata about the request. This will often be used just for initial debugging and troubleshooting. There is a check box on the configuration titled Include metadata in response. If this is checked, then a meta node will be present in the JSON and XML output. For CSV, the meta-data is output exists as HTTP headers that are prefixed with x- (e.g. x-rowCount and x-PeopleSoftUser)

JSON Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{ "data": [
    {
      "full_name": "Joseph Aranda",
      "date_of_birth": "1980-02-18"
    }
  ],
  "meta": {
    "acceptHeader": "application/json",
    "rowCount": 1,
    "sqlIDExecuted": "2b4c5484-bdd8-4096-8742-5ea7ceb1a6e5",
    "QueryString": "NULL",
    "URLPath": "person/FA0010/",
    "success": true,
    "finalSQL": "SELECT NAME, BIRTHDATE FROM PS_PERSON A,\n PS_PERSON_NAME N\nWHERE A.EMPLID = N.EMPLID  \nand A.EMPLID = 'FA0010'",
    "productVersion": "1.0",
    "PeopleSoftUser": "Z_TEST_API_USER",
    "PeopleSoftRequestID": "157ddb87-2b3a-11ea-8510-b3b9e4a7f95f"
  }
}

XML Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?xml version="1.0"?>
<response>
  <data>
    <row>
      <full_name>
        <![CDATA[Joseph Aranda]]>
      </full_name>
      <date_of_birth>
        <![CDATA[1980-02-18]]>
      </date_of_birth>
    </row>
  </data>
  <meta>
    <acceptHeader>
      <![CDATA[application/xml]]>
    </acceptHeader>
    <rowCount>1</rowCount>
    <sqlIDExecuted>2b4c5484-bdd8-4096-8742-5ea7ceb1a6e5</sqlIDExecuted>
    <QueryString>
      <![CDATA[NULL]]>
    </QueryString>
    <URLPath>
      <![CDATA[person/FA0010/]]>
    </URLPath>
    <success>True</success>
    <finalSQL>
      <![CDATA[SELECT NAME, BIRTHDATE FROM PS_PERSON A,
 PS_PERSON_NAME N
WHERE A.EMPLID = N.EMPLID  
and A.EMPLID = 'FA0010']]>
    </finalSQL>
    <productVersion>1.0</productVersion>
    <PeopleSoftUser>Z_TEST_API_USER</PeopleSoftUser>
    <PeopleSoftRequestID>4ca45d31-2b3a-11ea-8510-b3b9e4a7f95f</PeopleSoftRequestID>
  </meta>
</response> 

CSV Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Connection: close
Date: Mon, 30 Dec 2019 19:19:35 GMT
Content-Length: 77
Content-Type: text/csv; encoding="UTF-8"
Content-Encoding: gzip
x-success: True
x-PeopleSoftRequestID: 50e2c1c6-2b39-11ea-8510-b3b9e4a7f95f
x-acceptHeader: text/csv
x-sqlIDExecuted: 2b4c5484-bdd8-4096-8742-5ea7ceb1a6e5
x-productVersion: 1.0
x-rowCount: 1
x-URLPath: person/FA0010/
x-PeopleSoftUser: Z_TEST_API_USER

"full_name","date_of_birth"
"Joseph Aranda","1980-02-18"

Caching

  • Minutes to Cache Response: You can optionally configure to have the integration broker cache the response. This can be done for performance reasons if the SQL is very “expensive”.

SWS has an ability to tell the RESTListeningConnector servlet to cache the response. This caching is provided by the built in caching mechanism provided by PeopleTools REST functionality. It can speed up requests for expensive SQL where you do not expect the result to change very often. I would read the PeopleSoft REST Caching integration broker book section before using this. There are some known caveats.

FAQ

How will SWS impact our upcoming upgrade?

The SWS module was designed to have almost no upgrade ramifications. The objects delivered are completely “bolt-on” and there are no delivered objects that are modified. Normally during the initial upgrade pass your upgrade team will keep all “Bolt-on objects”. This will include the SWS code. This means that the code will come forward. If for some reason the code gets deleted during your upgrade pass you can easily re-import it.

Is META-SQL allowed

Yes, the SQL you configure in SWS is executed using CreateSQL and SQL Fetch PeopleCode methods. Any META-SQL or META-VARIABLEs that are supported in that context should be valid.

What is a GUID and why is it used as a database key?

A GUID is a globally unique identifier. We decided to use this as the database key in order to easier facilitate migrating SWS setup between databases. The GUID will be unique almost everywhere.

Examples

Below we will walk through some example setups to show you how setup and use the different options of SWS.

API User Setup

In all the examples below, we are going to be using the sample user we setup in the documentation above called Z_TEST_API_USER.

Person Example 1

In this example we are going to setup a simple person web service.

  • Inputs
    • EMPLID in URL Path
  • Outputs
    • First name
    • Last name
    • Formal Name
    • Birth Date

Here is a screenshot of the setup.

A few things to note:

  1. The path is /person/{{emplid}} where the named emplid shows up in the parameters section.
    • This name parameter has a max lenght of 10.
  2. We are renaming the columns from the PeopleSoft name to a custom name.
  3. We are asking for meta-data

When we look at the second tab we get an example of how to call this service.

Here is the request in HTTP Syntax for JSON output.

GET http://psft-cs-856.c.peoplesoftdemo-1470578694381.internal:8000/PSIGW/RESTListeningConnector/PSFT_CS/CHG_SWS/person/ADSAT002 HTTP/1.1
User-Agent: vscode-restclient
Authorization: Basic Wl9URVNUX0FQSV9VU0VSOnByb2N0b3ItY29uc3VsYXItZXN0aGVyLWh1bGwtZmxvb2Q=
Accept: application/json
accept-encoding: gzip, deflate

Here is the HTTP Response.

HTTP/1.1 200 OK
Connection: close
Date: Thu, 09 Jan 2020 05:58:40 GMT
Content-Length: 451
Content-Type: application/json; encoding="UTF-8"
x-success: True
x-PeopleSoftRequestID: 1739ebd0-32a5-11ea-999e-fb60e7da7901
Content-Encoding: gzip
{
  "data": [
    {

      "firstName": "Christie",
      "lastName": "Lamstadt",
      "formalName": "Christie Lamstadt",
      "dateOfBirth": "1984-07-26"
    }
  ],
  "meta": {

    "acceptHeader": "application/json",
    "rowCount": 1,
    "sqlIDExecuted": "2b4c5484-bdd8-4096-8742-5ea7ceb1a6e5",
    "QueryString": "NULL",
    "URLPath": "person/ADSAT002",
    "success": true,
    "finalSQL": "SELECT FIRST_NAME, LAST_NAME, NAME_FORMAL, BIRTHDATE FROM PS_PERSON A,\n PS_PERSON_NAME N\nWHERE A.EMPLID = N.EMPLID  \nand A.EMPLID = 'ADSAT002'",
    "productVersion": "1.0",
    "PeopleSoftUser": "Z_TEST_API_USER",
    "PeopleSoftRequestID": "1739ebd0-32a5-11ea-999e-fb60e7da7901"
  }
} 

This would have been the XML output body if you would have asked for XML in the header.

<?xml version="1.0"?>
<response>
  <data>
    <row>
      <firstName>
        <![CDATA[Christie]]>
      </firstName>
      <lastName>
        <![CDATA[Lamstadt]]>
      </lastName>
      <formalName>
        <![CDATA[Christie Lamstadt]]>
      </formalName>
      <dateOfBirth>
        <![CDATA[1984-07-26]]>
      </dateOfBirth>
    </row>
  </data>
  <meta>
    <acceptHeader>
      <![CDATA[application/xml]]>
    </acceptHeader>
    <rowCount>1</rowCount>
    <sqlIDExecuted>2b4c5484-bdd8-4096-8742-5ea7ceb1a6e5</sqlIDExecuted>
    <QueryString>
      <![CDATA[NULL]]>
    </QueryString>
    <URLPath>
      <![CDATA[person/ADSAT002]]>
    </URLPath>
    <success>True</success>
    <finalSQL>
      <![CDATA[SELECT FIRST_NAME, LAST_NAME, NAME_FORMAL, BIRTHDATE FROM PS_PERSON A,
 PS_PERSON_NAME N
WHERE A.EMPLID = N.EMPLID  
and A.EMPLID = 'ADSAT002']]>
    </finalSQL>
    <productVersion>1.0</productVersion>
    <PeopleSoftUser>Z_TEST_API_USER</PeopleSoftUser>
    <PeopleSoftRequestID>ce8b6315-32a5-11ea-999e-fb60e7da7901</PeopleSoftRequestID>
  </meta>
</response> 

Here is the CSV output if you would have asked for it.

"firstName","lastName","formalName","dateOfBirth"
"Christie","Lamstadt","Christie Lamstadt","1984-07-26"