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:
[
{
"employeeEmail": "employee@domain.com",
"managerEmail": "manager@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "12345"
},
{
"employeeEmail": "manager@domain.com",
"managerEmail": "ceo@domain.com",
"policyID": "0123456789ABCDEF",
"employeeID": "34567"
}
]
[
{
"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"],
"defaultTags": ["Engineering", "North America"]
},
{
"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 |
| shouldRemoveFromUnassignedPolicies | Boolean | Whether to remove an employee from policies they're not direcly assigned to via policyID, additionalPolicyIDs, or as another employee's managerEmail. Defaults to false. |
| defaultTags | JSON Array | Default tag values when the employee creates an expense on that policy. |
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 (managerEmail), 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 |