Expense Audit APIs

Overview

The purpose of this document is to provide an overview of the various API’s and Webhooks which are being used by AppZen as a part of authentication and establishing communication with external systems. These API’s are organised around REST. Our API has predictable resource-oriented URLs, accepts form-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes and verbs.


The journey of AppZen and customer system integrations begins with authentication and proceeds towards ingestion of reports from the customer's systems to AppZen's system. AppZen audits these expense reports and assigns them a risk score (Low, Medium, High). Some of the expense reports might need Auditor’s attention to move further in the system for reimbursement. The auditor can take action on such reports as per the company’s T&E policies.

Authentication

AppZen performs authentication and establishes connectivity post-validation with the customer's systems through OAuth 1.0 and OAuth 2.0.

OAuth 1.0 Authentication

What is OAuth 1.0?

OAuth (short for "Open Authorization") generally provides clients a "secure delegated access" to server resources on behalf of a resource owner without providing credentials. 

AppZen uses an API-Key mechanism to validate calls to the API (OAuth 1.0). It uses customer_id and API Key combination to allow access to the public APIs. 

Authentication for these APIs is done based on the following fields provided by the AppZen Support team.

  1. customer_id: Each customer will have a unique value for this field. 
  2. x-api-key: Each partner/integration will have a unique value. The same value has to be used for all customers belonging to a particular integration.
  3. customer-key: Each customer will have a unique value in this field.

OAuth 2.0 Authentication

Overview

What is OAuth 2.0 and how it works?

The OAuth 2.0 authorization framework is a protocol that allows a user to grant a third-party website or application access to the user's protected resources without necessarily revealing their long-term credentials or even their identity. AppZen thereby uses advanced authentication methods when users interact with its systems. AppZen would bring in self-serve capabilities in the future. Do keep a watch on support.appzen.com for product updates and release notes.

Why OAuth 2.0 is secure?

OAuth 2.0 is a secure, open data-sharing standard that should be built into every app. This authentication and authorization standard protects user data by providing access to the data without revealing the user's identity or credentials.

What is OAuth 2.0 in REST API?

In OAuth 2.0, the following three parties are involved:

  • The user possesses data that is accessed through the API and wants to allow the application to access it.
  • The application is to access the data through the API on the user's behalf.
  • The API controls and enables access to the user's data.

Objective

AppZen’s external APIs support API key-based authentication (OAuth 1.0), but to provide additional security, we will upgrade the authentication to OAuth 2.0. This would add an additional layer of security, limiting access to only specific APIs instead of exposure to all.

Parameters

  • ClientID
  • Client Secret
  • Scope
  • Time Validity of bearer token

Steps

1.Request for clientID and clientSecret

To establish an OAuth 2.0 authentication flow, AppZen will need to create a client credential for the customer. The customer will be provided with clientID and clientSecret by the AppZen Support Team, which would be required to generate ‘Access tokens’. AppZen will allow the regeneration of tokens (if required) from UI.
 

2. Client request for OAuth 2.0 token
The client requests a token to AppZen's API gateway for authentication and validation. The request format will look something like this-

curl --request POST 
--url <api.AppZen.com>
--header 'accept: application/json' 
--header 'authorization: Basic <base 64 encoded "clientId:clientSecret">' 
--header 'cache-control: no-cache' 
--header 'content-type: application/x-www-form-urlencoded' 
--data 'grant_type=client_credentials&scope=expense.read,expense.readWrite'


3. Response to a client request for an OAuth 2.0 token

AppZen will verify the provided clientID and clientSecret combination; if credentials are valid, a valid OAuth 2.0 token will be returned to the client, the format of the token will be something as follows-

{
"token_type": "Bearer",
"expires_in": 3600,
"access_token": 

"eyJraWQiOiJHZXo5VW9LQ1RDVVFrWDJCRHhiYTFFYTNpcEhBN2FhTzk2ZXliUnZQZGQwIiwiY
WxnIjoiUlMyNTYifQ.eyJ2ZXIiOjEsImp0aSI6IkFULmprM05mbEVyS0NUa1BOdWhzXzhzeGN2
aTl6eXdVaGhISU50ck8wZm03MG8iLCJpc3MiOiJodHRwczovL2FwcHplbi5va3RhLmNvbS9vYX
V0aDIvYXVzOGo4ZGg5c3doVXd2NTU0eDciLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAi
LCJpYXQiOjE2NjAxOTQ2MjQsImV4cCI6MTY2MDE5ODIyNCwiY2lkIjoiMG9hOG1lcGw0cFJkTj
JWbnM0eDciLCJzY3AiOlsicmVhZFdyaXRlIl0sInN1YiI6IjBvYThtZXBsNHBSZE4yVm5zNHg3
IiwiY3VzdG9tZXJJZCI6IjEyMyJ9.HFezKutng9YS_Y-cHHq2wMSxFU-
RRh4tEl85QKxC2Vy2DugCp3xiaRmJtMro0fCC41qGxWljT4jijzaJyRPd_JfGAgAf34Ge7BuOy
u3EaaC9yvzkw24_HBb4altTRTT1PVO9XhZ1OqkouyGlRp2_qgVapEgYaNdKd8ZHQzn-o
GHZyXApbx7_e-Ky8N9Pbj9P3ePGygek-_AqUolErGlToo9nGfqdqAvHDKr_UcrTPyNLyHibG7kl5_r2ReYv267yS5BRO5kkcWMrhEsapjk
Ozho5BbZypsjJ7HQIk_RlORXtBAv2djCo48pO1chbc3yUL74iFDVW6Flqsl1wgzOkQg",

"scope": "expense.read expense.readWrite"
}

3. Custom AppZen Scopes

Expense Audit: expense.read, expense.readwrite

Autonomous AP: Not supported currently

4. Request to AppZen APIs

The client now uses the access token received in the previous step as a bearer token to access the AppZen resource.

AppZen will validate this token and grant access to the required resource.

5. The bearer token will expire in 60 mins

  1. Users will need to refresh in case of expiry to regain access.
  2. Users will not gain any access beyond the scope of the original grant.

Note

  • Currently, AppZen supports OAuth 1.0 for external API authentication. We plan to deprecate OAuth 1.0 support soon after the release of OAuth 2.0. Watch out for updates on support.appzen.com 
  • We'll enable a self-serve feature for OAuth 2.0 authentication soon, where users can have the OAuth 2.0 setup with AppZen. A user should have a Functional Admin role to perform this activity. Keep a watchful eye on support.appzen.com for updates.

Expense Report Ingestion API

Generate the URL to Upload Attachment 
 

GET /reports/{external_report_id}/images/upload-url

This would generate a pre-signed URL to upload the attachment to the AppZen environment. Also, as a part of its response, you will get the 'file_id'. This 'file_id' is the name/key by which your attachment would be referred by the AppZen system. You will be required to mention the value of this field while uploading a report.

Sample Request

curl --location --request GET 

"https://api.AppZen.com/expense-ingestion/reports/12AB34CD/images/upload-url" 

--header "x-api-key: EBRxxxxxxxxxxxxxxxxxlij58v" 
--header "customer_id: 9xxx9" 
--header "customer-key: 620bxxxxxxxxxxxxxa355b9dff7"

Sample Response

{
 "Status": "Success",
   "msg": "Successfully generated URL for uploading image",
   "image_upload_url_details": 
{
   "url": 
"https://AppZen-images.s3.amxxxxws.com/9xxx9/797f3316-xxx-xxxx-xxxx-72e33d789729?
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date= 20200921T132222Z&X-Amz-SignedHeaders=host&
X-Amz-Expires=300&X-Amz-Credential=AKxxxxxxZ4TKKxxxP3A%2F20200921%2Fus-east-1%2Fs3%2Faws4_request&
X-Amz-Signature=882xxx83835xxxxxxxxxxxxxx091fbfa5bxxxxxxxxa2f2dxx3fd3", "file_id": "797f3316-xxx-xxxx-xxxx-72e33d789729" }, "external_report_id": "12AB34CD" }

Upload Attachment using the URL Generated Above

PUT /{s3-upload-URL}

Sample Request

curl --location --request PUT 

"https://AppZen-images.s3.amxxxxws.com/9xxx9/e2d48031-000e-4bd4-9080-75565cff8b75?
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20200921T132600Z&X-Amz-SignedHeaders=host&
X-Amz-Expires=300&X-Amz-Credential=AKIAIxxxxxKKWxxxP3A%2F20200921%2Fus-east-1%2Fs3%2Faws4_request&
X-Amz-Signature=91ae27af24a85xxxxxxx1c3fd630d0xxxxxxxx2616cdxxxx6bd" --header "customer_id: 9xxx9" --header "Content-Type: application/x-www-form-urlencoded" --header "x-api-key: XLD9TXxxxxxxxGaGhHxxxxxxxxVUKegx" --data-binary "{path_of_the_image}"

Sample Response

Status Code: 200 [with empty response]

Upload Report Details using API to Ingest the Report 

PUT /reports/{external_report_id}

This API is used to ingest the expense reports in AppZen. It is called after all the required attachments are uploaded into the system. The image id will be populated in the request body along with other expense report details.

Sample Request

curl --location --request PUT "https://api.AppZen.com/expense-ingestion/reports/12AB34CD" 
--header "x-api-key: EBRxxxxxxxxxxxxxxxxxlij58v" 
--header "customer_id: 9xxx9" 
--header "customer-key: 620bxxxxxxxxxxxxxa355b9dff7" 
--header "Content-Type: application/json" 
--data "{

  "expense_report": {
    "date": "2020-09-01T14:23:57.014Z",
    "country": "US",
    "amount": "1100.00",
    "report_name": "AZAuto891 1335077649",
    "last_updated_at": "2020-09-01T14:23:57.014Z",
    "elines": [
      {
        "el_type": "Hotel Room Charges",
        "el_curr": "USD",
        "missing_receipt": "N",
        "expense_line_iId": 1,
        "elItemizedFlag": "false",
        "eldetails": [],
        "elamount": "787.27",
        "elpaidamount": "787.27",
        "elpurpose": "Expense Link Description",
        "eldate": "2020-08-01T14:23:57.014Z",
        "eline_id": "1",
        "elpaidcurr": "USD",
        "elconvrate": "0.145507246376813",
        "elinetype": "CREDIT",
       "elpersonalflag": "N",
       "elcostcenter": "605",
       "elvatflag": "false",
       "el_image_id": [
       "d3f4e3bc-9ec6-xxxx-xxxx-xxx445762b05"
        ]
      },
      {
        "eltype": "Hotel Room Charges",
        "elcurr": "USD",
        "missingReceipt": "N",
        "expenseLineId": 2,
        "elItemizedFlag": "false",
        "eldetails": [],
        "elamount": "787.27",
        "elpaidamount": "787.27",
        "elpurpose": "Expense Link Description",
        "eldate": "2020-09-01T14:23:57.014Z",
        "elineid": "2",
        "elpaidcurr": "USD",
        "elconvrate": "0.145507246376813",
        "elinetype": "CREDIT",
        "elpersonalflag": "N",
        "elcostcenter": "605",
        "elvatflag": "false",
        "elimageid": [
          "e2d48031-000e-xxxx-xxxx-75565cff8b75"
        ]
      }
    ],
    "external_template_name":"external_template_name_trial_2",
    "external_template_id": "external_template_id_trial_2",
    "userName": "john smith",
    "userid": "pkuid000001",
    "createdAt": "2020-09-01T14:23:57.014Z",
    "emailAddress": "john.smith@domain.com",
    "countryCode": "USA",
    "customerId": 9xxx9,
    "curr": "USD",
    "desc": "Report Description 2",
    "status": "SUBMITTED TO AppZen"
  }
}"

Sample Response

{
    "Status": "Success",
    "msg": "Successfully processed submitted report"
}

Audit Result API

This set of APIs are meant for fetching the audit results for all the expense reports which have been audited till now. There are two ways by which the audit result can be fetched - for a single expense report and in bulk.

Fetch Audit Results for a Single Expense Report

GET /exp-audit–results/customers/{customer-id}/reports/{external-report-id}

Sample Request

curl --location --request GET 

"https://api.AppZen.com/expense-audit-results/customer/{customer_id}/reports/{external_report_id}" 

--header "x-api-key: EBRxxxxxxxxxxxxxxxxxlij58v" 
--header "customer_id: 9xxx9" 
--header "customer-key: 620bxxxxxxxxxxxxxa355b9dff7

Sample Response

{
  "customer_id": "9xxx9",
  "external_report_id": "e2xxxxxxxx210",
  
  "current_risk_level": "HIGH",
  "original_risk_level": "HIGH",
  "computed_risk_level": "HIGH",
  "audit_result_created_at": "2019-01-03 09:11:11.598",
  "header_level_risk_details": [
    {
      "rule_name": "Report Unauthorized Expenses",
      "current_risk_level": "LOW",
      "original_risk_level": "LOW",
      "computed_risk_level": "LOW",
      "risk_message": "No unauthorized items were detected in report.",
      "parameters": null
    },
    {
      "rule_name": "Daily meal limit check",
      "current_risk_level": "MEDIUM",
      "original_risk_level": "MEDIUM",
      "computed_risk_level": "MEDIUM",
      "risk_message": "Daily meal cost is greater than threshold",
      "parameters": null
    },
    {
      "rule_name": "Report Personal Credit Card Check",
      "current_risk_level": "LOW",
      "original_risk_level": "LOW",
      "computed_risk_level": "LOW",
      "risk_message": "No personal credit cards were detected in report.",
      "parameters": null
    }
  ]
"line_level_results": [
    {
      "external_exp_line_id": "l12345987654",
      "line_level_risk_details": [
        {
          "rule_name": "Unauthorized Expenses",
          "current_risk_level": "LOW",
          "original_risk_level": "LOW",
          "computed_risk_level": "LOW",
          "risk_message": "No unauthorized items were detected.",
          "parameters": null
        },
        {
          "rule_name": "Amount Verification",
          "current_risk_level": "LOW",
          "original_risk_level": "LOW",
          "computed_risk_level": "LOW",
          "risk_message": "Amount below threshold.",
        } 
        {
          "rule_name": "Weekend Expense",
          "current_risk_level": "LOW",
          "original_risk_level": "LOW",
          "computed_risk_level": "LOW",
          "risk_message": "This expense is not incurred during weekend days.",
          "parameters": null
        },
{
         "rule_name": "Duplicate Within A Report",
          "current_risk_level": "LOW",
          "original_risk_level": "LOW",
          "computed_risk_level": "LOW",
          "risk_message": "No duplicate expense line was detected within the same report",
          "parameters": null
        }
      ]
    }
  ]

Fetch Audit Results for Expense Reports in Bulk 

POST /exp-audit-results/customer/{customer-id}/reports

This API will provide you with the audit-results in paginated format. User needs to pass the report ids in “report_id _in” field. Report ids must be comma separated.

Sample Request

curl --location --request POST 

"https://api.AppZen.com/expense-audit-results/customer/9xxx9/reports" 
--header "x-api-key: EBRxxxxxxxxxxxxxxxxxlij58v" 
--header "customer_id: 9xxx9" 
--header "customer-key: 620bxxxxxxxxxxxxxa355b9dff7" 
--header "Content-Type: application/json" 
--data "{

"from_audit_date" : String
"fron_submission_date" : String
"page_size" : 5,
"page_number" : 0,
"report_id_in" : ["12AB34CD",”998899ED”]
"report_id_not_in" : String
"sort_direction" : String
"sort_field" : String
"to_audit_date" : String
"to_submission_date" : String
}"

Sample Response

While fetching the audit results for bulk reports, the Response body will consist of similar information like we get while fetching the audit result for a single expense report, but would consists output for all the expense reports specified in the Request command.

List of filters that can be applied while fetching audit results in bulk:

  • Range of Submission Date
    • from_submission_date: This would be inclusive
    • to_submission_date: This would be exclusive
  • Range of Audit Date
    • from_audit_date: This would be inclusive
    • to_audit_date: This would be exclusive
  • report_id_in: Collection of external_report_id you want to fetch the audit results for
  • report_id_not_in: Collection of external_report_id you do not want to fetch the audit results for
  • page_number: zero-based page index, for which you want to fetch the audit results
  • page_size: Number of results per page. The maximum limit of page_size would be defined by AppZen (Currently 200). Suppose you do not provide any input for this field or provide invalid input (0>=value>200). In that case, the system will default the value of page_size to its maximum (200 for now)
  • sort_field: field name, upon which sorting should be applied (for all available results which fulfil the filter mentioned above criteria) before computing the paginated results.
    • Fields supported for sorting audit results are as follows:
      • current_risk_level
      • original_risk_level
      • computed_risk_level
      • created_at: This is the default field for sorting.
    • Note: If unsupported fields are provided or if sort_field is empty, then the sorting will default to the field "created_at"

Sorting of the fetched result:

The result set can be fetched in sorted order based on the field and direction provided.

Sort_direction:

The order of sort for the available results prior to computing the paginated results (ASC - for ascending sort order and DESC - for descending sort order)

Audit Action API 

Objective

For the customers with API-based integration, Audit Action API is a way to push out the information related to actions taken on the expense report to the source system. Whenever an auditor takes any action on an expense report in AppZen, the event with all associated data will be sent to the external system.

Some of the salient specifications are as below -

  • AppZen will use this API to post auditor action to customer systems
  • AppZen will try sending webhook 3 times
  • Currently following audit workflow events are supported:
    • Automatic Approved
    • Automatic Reject
    • Manual Approved
    • Manual Reject

Webhook Configuration

Since AppZen would need to send data to the customer's systems, the webhook is designed in such a way that it would do the following things before reports are actioned at AppZen -

  1. Send out test events to verify the connection, so that there is less probability of communication errors during live workflows and obtain the HTTP Response code as 200, after which communication is considered as successful.
  2. AppZen would support multiple Authentication mechanisms so that the external systems can use one of those to receive webhook.

The below authentication mechanisms are supported - 

1. API Key-based Authentication - If any client wants to use API Key-based Authentication, then the webhook configuration will be something as follows

{
	"customerId" : xx32,
	"appzenProduct" : "EXPENSE",
	"externalPlatform" : "WEBHOOK",
	"baseUrl" : "<ExternalSystemURL>",
	"authnType" : "ApiKey",
	"authnConfigName" : "identifierName",
	"credentialsJson" : {
		"x-api-key" : "providedAPIKey"
	}
}

2. OAuth 2.0 Support - Clients needing OAuth 2.0 support will have to configure the following information, by supporting the client_credentials flow.

{
	"customerId" : xx32,
	"appzenProduct" : "EXPENSE",
	"externalPlatform" : "WEBHOOK",
	"baseUrl" : "<ExternalSystemURL>",
	"authnType" : "OAuth2.0",
	"authnConfigName" : "identifierName",
	"credentialsJson" : 
{
	"client_id" : "id",
	"client_secret" : "secret",
     "scopes" : <optional>,
"token_url" : <URL which needs to be called for getting the token>
"token" : {<store actual Client Auth Token to be used for API calls>}
}
}

Sample Webhook Payload

{
  "report_Id" : "XYZ",
  "audit_status" : "MANUAL_AUDIT_APPROVED",
  "auditor_comments" : "{comments}",
  "actioned_by" : "{auditor_email}",
  "event" : "report_status_change",
}

Appendix

Errors/Status Code

The first digit of the status code defines its class:

1XX (informational)

The request was received, continuing the process.

2XX (successful)

The request was successfully received, understood, and accepted.

3XX (redirection)

Further action needs to be taken in order to complete the request.

4XX (client error)

The request contains bad syntax or cannot be fulfilled.

5XX (server error)

The server failed to fulfill an apparently valid request. 

REST API

Representational State Transfer (REST) are the Web APIs that adhere to the REST architectural constraints and are called RESTful APIs.

Webhook

A webhook is an HTTP-based callback function that allows lightweight, event-driven communication between 2 application programming interfaces (APIs). The name webhook is a simple combination of web, referring to its HTTP-based communication, and the hooking programming function that allows apps to intercept calls or other events that might be of interest. Webhooks hook the event that occurs on the server app and prompt the server to send the payload to the client via the web.

Legends

  • Clients - Clients are users who want to access information from the web. The client can be a person or a software system that uses the API
  • Resources - Resources are the information that different applications provide to their clients.
  • Access Token Access Token is a string that the OAuth client uses to make requests to the resource server.
  • Bearer Token A Bearer Token is an opaque string, not intended to have any meaning to clients using it. Some servers will issue tokens that are a shortstring of hexadecimal characters, while others may use structured tokens.
  • Refresh Token An OAuth Refresh Token is a string that the OAuth client can use to get a new access token without the user's interaction.
  • clientID and clientSecret - The client needs to authenticate themselves for any request.

 

AppZen, Inc.    Confidential

 

 

 

Was this article helpful?
0 out of 0 found this helpful

Comments

0 comments

Please sign in to leave a comment.