This is it, folks; we seem to have found the worst API for an “enterprise-grade” app, with horrible documentation to really bring it full circle.
This leads me to why I wrote the post, to provide a clear guide on what I wish I had known when I headed down this rabbit hole. Hopefully, others will find it helpful. A huge shoutout to Jacob Waters in MacAdmins for pointing me in the right direction. Appreciate ya, sir.
If you want to skip all this and grab my workflow, head here and tweak it as you need.
“Harrison, why do I need to do all this workflow stuff? Doesn’t Expensify have provisioning? They told me they did?”
Ah, but you would be mistaken.
The Okta integration cannot:
- Provision Users in Expensify
- Update User Attributes in Expensify
- Group Push from Okta to Expensify
- Import Groups from Expensify to Okta
“So building a workflow can’t be so bad, right?”
Oh you’re so wrong. Grab a coffee and pull up a chair.
Authentication
This is the most simple part. Navigate to the “integrations center” and grab a partnerUserID
and partnerUserSecret
. Store them somewhere safe. Yes, you have to hard-code them into your workflow; it’s very secure. 😒
Format
All requests go to the same endpoint, https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations and must be wrapped in this requestJobDescription
body.
Inside this requestJobDescription
there are three values that you must set: type, credentials, inputSettings
Everything is a POST
, but the type specifies the actual REST verb.
Putting it together and grabbing the policy IDs
Each policy has a corresponding ID we need to get to use downstream. Depending on your org, this could be quite simple, but in our case, we have multiple policies correlating to each geo, so I start by listing them all.
requestJobDescription={
"type":"get",
"credentials":{
"partnerUserID": "XXX",
"partnerUserSecret": "YYY"
},
"inputSettings":{
"type":"policyList"
}
}
If we go to the API documentation for Policy list getter, we will see that the only thing we have to specify is the type, which is inside inputSettings, which is inside requestJobDescription 🙃
I didn’t do this portion in OWF. I used Insomnia, which is my favorite API client. Since the values don’t change, I grab them and use an Assign
card in the actual workflow. This section is more or less here as a primer to show you the weird format calls are structured in.
Time to Create a User
This workflow is a helper flow in my onboarding ecosystem, so we start by grabbing the userID upstream and using a Read
card.
Next is the Lookup
card, which translates our geos to the correct policy, and then an Assign
card. Check it out below.
Just like the previous section, each API call is wrapped in a requestJobDescription
, which must contain:
- type
- credentials
- dataSource
- inputSettings
The inputSetting
doesn’t change; it’s included in every call. 😒
"inputSettings": {
"type": "employees",
"entity": "generic"
}
At the beginning of the workflow, I build the inputSetting with a Construct
card, set it, and forget it.
And I do the same for the credentials.
Anddddd for the headers.
To summarize, at this point, we have the policyID, the credentials, inputSettings, and headers, which we can now freely call downstream.
One of the optional values we can pass is onFinish, just a weird way of saying a notification is sent at the end of the action. I use it to send an email to the manager. We can also build that here as well like such:
Now we can start to bring it all together!
I’ll explain each part here quickly since it didn’t initially stick for me, either.
type is update
, even though we are creating a user.
dry-run is True in prod. This can actually be helpful to review output.
credentials from key/value we created previously.
dataSource is request
. Dunno why 🤷🏻♂️
inputSettings is that odd part that never changes.
onFinish is the email we will send to manager.
setEmployeePrimaryPolicy sets the policy as the primary.
We use a compose card to assign the variable, since it must be requestJobDescription=
And we haven’t even started the user attribute section yet 🥱
We should probably get to that point.
API needs the form in an array so we use a construct
list card to build the individual user, then the object
card to create the Employees
and then finally assign it to data=
variable. 😵💫 It’s like Russian nesting dolls.
Finally! Bring it all together
Using an API card, we make a POST
with the three sections we worked so hard to build previously.
requestJobDescription
output gets dragged in. So do the headers and the data
which is really the Employees
array we built previously. All together it looks like:
Error Handling
As you can probably imagine, this part of the API isn’t a walk in the park, either. Expensify returns responseCode
instead of statusCode
. So we parse the returned JSON body in order to fetch the responseCode
and determine if request was successful. If it fails, we have a message sent to Slack.