Workday Private Web Services with Python and Zeep
We came up with a project idea where we'd like to try to manage (extend) scheduled reports in Workday through ServiceNow tickets because our employee experience today kind of sucks. The processes just kind of stop silently and users eventually realize they stopped getting the report they depend on.
Making this process better could follow some sort of phased approach where:
- We generate alerts/notifications and ServiceNow tickets for report processes expiring within N days.
- There is a request/approval workflow in ServiceNow to extend the process.
- When the ticket is closed, an API request should go out to Workday to automatically extend the process.
The rest of this post is going to focus on step #3. I'm going to put together a prototype of how we could retrieve and update the data for a scheduled future process.
Find a Scheduled Future Process API
Workday has a few places we can look for references to an API to automate scheduling processes:
- Production (public) SOAP API .... Nothing there.
- REST API ... Also nothing there.
Okay, maybe Workday will give us a hint in View Security for Securable Item.
So there is one!! Workday is hiding it away somewhere for OX and implementers....
Private web services
Workday has a number of not-really-documented "private" API's. You used to be able to find them from iLoads task pages. They don't have a documented API, I have no idea what kind of SLA's or guarantees there are around breaking changes. For all I know, they could disappear on the next Workday release, so build at your own risk.
One of the more common ones I've worked with is the Core_Implementation_Service
wsdl:
wsdl = "https://{hostname}.workday.com/ccx/service/{tenant}/Core_Implementation_Service/v40.0?wsdl"
This one happens to have exactly what we're looking for:
Get_Scheduled_Future_Process_Request
Put_Scheduled_Future_Process_Request
Exploring the API
I already mentioned that the API is undocumented by Workday. The way I've historically done this is with SoapUI Open Source. For a given WSDL, SoapUI will generate a bunch of sample requests.
As an example, this is what it generated for Get_Scheduled_Future_Process_Request. Note: I added the <wsse:Security>
bits so that it was an actually usable code snippet. SoapUI and Workday Studio Web Service Tester all love to leave that bit out by default.
You could fill this in, post it with your favorite HTTP client to Workday and if all goes well, it should work.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:bsvc="urn:com.workday/bsvc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1">
<wsse:UsernameToken>
<wsse:Username>{username}@{tenant}</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">{password}</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
<bsvc:Workday_Common_Header>
<!--Optional:-->
<bsvc:Include_Reference_Descriptors_In_Response>?</bsvc:Include_Reference_Descriptors_In_Response>
</bsvc:Workday_Common_Header>
</soapenv:Header>
<soapenv:Body>
<bsvc:Get_Scheduled_Future_Process_Request bsvc:version="?">
<!--You
have a CHOICE of the next 2 items at this level-->
<!--Optional:-->
<bsvc:Request_References>
<!--1
or more repetitions:-->
<bsvc:Scheduled_Future_Process_Reference bsvc:Descriptor="?">
<!--Zero
or more repetitions:-->
<bsvc:ID bsvc:type="?">?</bsvc:ID>
</bsvc:Scheduled_Future_Process_Reference>
</bsvc:Request_References>
<!--Optional:-->
<bsvc:Request_Criteria />
<!--Optional:-->
<bsvc:Response_Filter>
<!--Optional:-->
<bsvc:As_Of_Effective_Date>?</bsvc:As_Of_Effective_Date>
<!--Optional:-->
<bsvc:As_Of_Entry_DateTime>?</bsvc:As_Of_Entry_DateTime>
<!--Optional:-->
<bsvc:Page>?</bsvc:Page>
<!--Optional:-->
<bsvc:Count>?</bsvc:Count>
</bsvc:Response_Filter>
<!--Optional:-->
<bsvc:Response_Group>
<!--Optional:-->
<bsvc:Include_Reference>?</bsvc:Include_Reference>
<!--Optional:-->
<bsvc:Include_Scheduled_Future_Process_Data>?</bsvc:Include_Scheduled_Future_Process_Data>
</bsvc:Response_Group>
</bsvc:Get_Scheduled_Future_Process_Request>
</soapenv:Body>
</soapenv:Envelope>
To date, I've maintained XML like you see above and post that to Workday with the Python requests
or httpx
packages. Doing it that way can be a little fragile because you have to make that you enode/decode special characters correclty and that you or your code formatter doesn't accidently format the XML body into something that Workday rejects.
Python 'zeep'
I've been meaning to try out zeep since it appears to be the #1 Python SOAP client. It was a little slow and not-immediately-intuitive to get going (but is that not the case for everything related to SOAP?). My recommendation is to read the examples, and stumble (struggle) through it. It probably doesn't help that this WSDL is HUGE.
First up, inspect the wsdl file with:
python -m zeep $WSDL_URL >> about.txt
From this huge file of text, you can start to inspect and understand the different types and how you might use them for the diffent API operations. I'm not going to attempt to explain reading this file. I was stuck on the data types for a few hours and I used a lot of guess and check to get this working.
There are a few places to see more examples on the data structures and types:
- Zeep docs - Datastructures
- Query Workday API using Python and the Zeep package
- The post you're reading now.
Retrieving and Extending a Scheduled Future Process
"""
This is a quick script to demonstrate using zeep with Workday SOAP API's.
This script will:
1. Retrieve a Scheduled Future Process for a given Workday ID (WID)
2. Parse out the necessary response parts to eventually send a request to
update the process.
3. Change the end date of the Scheduled Future Process to a later date.
4. Submit a request to update the Scheduled Future Process.
"""
from datetime import date
from environs import Env
from zeep import Client
from zeep.wsse.username import UsernameToken
# Read in environment variables
env = Env()
env.read_env()
print("Let's go!")
# Create a new zeep client
client = Client(
wsdl=env.str("WSDL"),
wsse=UsernameToken(env.str("USERNAME"), env.str("PASSWORD")),
)
# Create the data structure for the Get_Scheduled_Future_Process request
get_request_dict = {
"Request_References": {
"Scheduled_Future_Process_Reference": [
{
"ID": {
"type": "WID",
"_value_1": env.str("WID"),
},
},
],
},
}
# Submit the get request and save the response
get_response = client.service.Get_Scheduled_Future_Process(**get_request_dict)
with open("_get_response.txt", "w") as f:
f.write(f"{get_response}")
# Parse the response
# To submit an updated Scheduled Future Process, we need two data structures:
# 1. Scheduled_Future_Process_Reference
# 2. Scheduled_Future_Process_Data
# Parse 1 of 2: Scheduled_Future_Process_Reference
process_reference = get_response.Response_Data.Scheduled_Future_Process[0]
process_reference = process_reference.Scheduled_Future_Process_Reference
# Parse 2 of 2: Scheduled_Future_Process_Data
process_data = get_response.Response_Data.Scheduled_Future_Process[0]
process_data = process_data.Scheduled_Future_Process_Data
process_data.Time_Based_Trigger_Data.Trigger_End_Date = date(2026, 12, 30)
# Create a data structure that combines the process reference and data
put_request_dict = {
"Scheduled_Future_Process_Reference": process_reference,
"Scheduled_Future_Process_Data": process_data,
}
# Submit a Put_Scheduled_Future_Process request to update the response
put_response = client.service.Put_Scheduled_Future_Process(**put_request_dict)
with open("_put_response.txt", "w") as f:
f.write(f"{put_response}")
# Go check the tenant to make sure it worked