Advanced employee updater introduction
The advanced employee updater is designed to allow dynamic and customizable employee provisioning and de-provisioning in Expensify. It will allow you to:
- Provision employees into Expensify policies based on customizable fields, such as employee department, country, job code, etc.
- De-provision employees from Expensify, based on customizable fields, such as termination date
- Assign employees to Expensify domain groups based on customizable fields, such as employee department, country, job code, etc.
- Automatically invite managers to policies they have subordinates on
- Automatically detect employee email address changes, and merge both addresses into the same account
- Import additional information from the employee feed into Expensify, to reuse for custom data export
API Principles
Employee feed examples with various amounts of information provided:
{
"Employees": [
{
"employeeEmail": "employee@domain.com",
"managerEmail": "manager@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "12345"
},
{
"employeeEmail": "manager@domain.com",
"managerEmail": "ceo@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "34567"
}
]
}
{
"Employees": [
{
"employeeEmail": "employee@domain.com",
"managerEmail": "manager@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "12345",
"firstName": "John",
"lastName": "Doe",
"customField2": "ABC123",
"approvalLimit": 12300,
"overLimitApprover": "audit@domain.com",
"isTerminated": false,
"workerStatus": "On Leave",
"additionalPolicyIDs": ["ABCDEF0123456789", "456789ABCDEF0123"]
},
{
"employeeEmail": "manager@domain.com",
"managerEmail": "ceo@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "34567",
"firstName": "Michael",
"lastName": "Scott",
"customField1": "ZZZ333",
"customField2": "BCD345",
"isTerminated": false
}
]
}
The job uses a JSON list of objects representing employees to provision.
Required employee fields
Field name | Type | Description |
---|---|---|
employeeEmail | String | The email address of the employee |
managerEmail | String | Who the employee should submit reports to |
employeeID | String | Unique, constant external identifier of the employee. It is used to detect email address changes |
policyID | String | The ID of the policy the employee should be invited to |
Optional employee fields
Field name | Type | Description |
---|---|---|
firstName | String | First name of the employee in Expensify. Does not overwrite values manually set by the employee in their Expensify account |
lastName | String | Last name of the employee in Expensify. Does not overwrite values manually set by the employee in their Expensify account |
customField1 | String | Custom field, displayed in the "Custom Field 1" |
customField2 | String | Custom field, displayed in the "Custom Field 2" |
approvalLimit | Integer | If passed, determines who the employee should forward the report to when they approve reports over overLimitApprover |
overLimitApprover | String | Who the manager should forward reports to if a report is over approvalLimit . Required if an approvalLimit is specified |
limitApprover | String | Alias for overLimitApprover |
workerStatus | String | If "On Leave", they won't be added as any employee's manager. They'll be replaced by their own manager |
isTerminated | Boolean | If set to true, the employee will be removed from the policyID |
domainGroupID | String | The ID of the domain group this domain member should be added to See note below. |
approvesTo | String | If a valid email address is passed, determines who the employee should forward the report to |
role | String | One of user , auditor , admin . If passed, specifies the role of the account in the policy in Expensify. Policy owners and the account running the request cannot have their role demoted from admin . |
additionalPolicyIDs | JSON array | List of additional policies the employee should be added to |
Request format
Request payload
- Via URL
{
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx",
"feedUrl": "https://somedomain.com/path/to/employeeData.json",
"feedUser": "expensify",
"feedPassword": "xxx"
},
"dataSource" : "download",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}
curl -X POST 'https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations' \
-d 'requestJobDescription={
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx",
"feedUrl": "https://somedomain.com/path/to/employeeData.json",
"feedUser": "expensify",
"feedPassword": "xxx"
},
"dataSource" : "download",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}'
- Directly in the request (parameter
data
)
{
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx"
},
"dataSource" : "request",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}
curl -X POST 'https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations' \
-d 'requestJobDescription={
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx"
},
"dataSource" : "request",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}' \
--data-urlencode 'data@myEmployeeData.json'
- Via SFTP
{
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx",
"sftp": {
"host": "sftp.domain.com",
"login": "expensify",
"password": "[xxx]",
"port": 22,
"filename": "/path/to/employeeFile.json"
}
},
"dataSource" : "sftp",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}
curl -X POST 'https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations' \
-d 'requestJobDescription={
"type": "update",
"dry-run" : false,
"credentials": {
"partnerUserID": "aa_api_domain_com",
"partnerUserSecret": "xxx",
"sftp": {
"host": "sftp.domain.com",
"login": "expensify",
"password": "[xxx]",
"port": 22,
"filename": "/path/to/employeeFile.json"
}
},
"dataSource" : "sftp",
"inputSettings": {
"type": "employees",
"entity": "generic"
},
"onFinish":[
{"actionName": "email", "recipients":"admin1@domain.com,admin2@domain.com"}
]
}'
The API call should be made to the regular endpoint https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations
. There are three ways to pass the employee data:
- Directly as a parameter of the request
- As a URL, that Expensify will load the data from (using Basic Authentication)
- Hosted on a SFTP server, that Expensify will load the data from
requestJobDescription
format
Name | Format | Valid values | Description |
---|---|---|---|
type | String | update |
|
credentials | JSON object | Contains your Expensify API credentials, and information to access the feed by URL if needed. | |
dataSource | String | download , request , or sftp |
Dictates how Expensify should retrieve your employee feed. |
inputSettings | JSON object | See inputSettings below. |
|
Optional elements | |||
dry-run | Boolean | true, false | If set to true, employees will not actually be provisioned or updated. Use this for development. |
onFinish | JSON array | See onFinish | You can configure the recipients list of email addresses that should receive a summary email of the changes made by the API. |
setEmployeePrimaryPolicy | String | none (default), new_employees , all_employees |
none : we will never update an employee's primary policy.new_employees : we will set an employee's primary policy to the policyID parameter if they were not a member of that policy yet.all_employees : we will update the primary policy for every employee. |
shouldFixApprovalChains | Boolean | true (default), false |
Dictates whether Expensify will automatically invite managers (submitsTo ), managers’ managers, and so on to policies where they approve reports, if it is not their primary policy. |
inputSettings
Name | Format | Valid values | Description |
---|---|---|---|
type | String | employees |
|
entity | String | Should always be set to generic . |
Response format
{
"responseCode": 200,
"dry-run": false,
"updatedEmployeesCount": 3,
"diff": {
"diffToAdd": {
"0123456789ABCDEF": [
"employee1@domain.com",
"employee2@domain.com"
],
"ABCDEF0123456789": [
"employee3@domain.com"
]
},
"diffToRemove": {
"B1C7903C636F4A51": [
"terminatedEmployee@domain.com"
]
}
},
"securityGroupEmployeesMap": {
"407184": [
"employee1@domain.com",
"employee2@domain.com"
],
"830936": [
"employee3@domain.com"
]
},
"skippedEmployees": [
{
"email": "employee6@domain.com",
"reason": "No policy found for 'Marketing'"
},
{
"email": "employee7@domain.com",
"reason": "Invalid manager email address 'manager@domain '"
}
]
}
Response codes
Response code | Meaning |
---|---|
200 | Success |
410 | Invalid input data, e.g. employee data is missing |
500 | Other |
Response data
Key | Meaning |
---|---|
dry-run |
Whether the job was run in dry-run mode, meaning employee data was not actually updated in Expensify |
updatedEmployeesCount |
Number of employees that were or would have been updated, depending on if the job was run in dry-run mode |
diff , diffToAdd , diffToRemove |
Email addresses of employees that were or would have been invited or removed from policies, grouped by policy IDs |
securityGroupEmployeesMap |
Email addresses of employees that were or would have been assigned to Domain Groups, grouped by domain group IDs |
skippedEmployees |
List of employees that could not be updated, with a reason why |