Purple Expense API Documentation
Machine-readable OpenAPI 3.0 specification: https://marketplace.moveworks.com/api/purple-suite/expense/openapi.json
The consolidated all-apps document at https://marketplace.moveworks.com/api/purple-suite/openapi.json can be narrowed to this app with ?apps=expense, or combined with other apps comma-separated (e.g. ?apps=expense,itsm).
Overview
Endpoints
GET /api/purple-suite/expense/trips
List business trips
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Tripidstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/trips
Create a business trip
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Trip
idstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonTripidstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/trips/{id}
Get a business trip by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonTripidstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/trips/{id}
Delete a business trip
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/trips/{id}
Edit a business trip
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Trip
idstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonTripidstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/expense/trips/{id}/approve
Pre-approve a trip
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
approved_bystring requiredapproval_notesstring
Responses
200- Trip approved
application/jsonTripidstringnamestring — Short label for the trip (e.g. 'SF Sales Summit 2026')purposestring — Business reason for the triptraveler_emailstringstart_datestringend_datestringoriginstring — Departure airport or city codedestinationstring — Primary destination airport or city codedestination_countrystring — ISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.is_internationalboolean — True when the destination country differs from the traveler's home country.statusstringestimated_costnumberactual_costnumber — Populated once the trip is in_progress or completedcurrencystringcost_centerstringproject_codestringapprover_emailstringapproved_bystringapproved_atstringapproval_notesstringcreatedAtstringupdatedAtstring
404- Trip not found
422- Trip cannot be approved in its current state
GET /api/purple-suite/expense/bookings
List travel bookings (reservations)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Bookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/bookings
Record a travel booking manually
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Booking
idstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/bookings/{id}
Get a travel booking by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/bookings/{id}
Delete a travel booking
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/bookings/{id}
Edit a travel booking
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Booking
idstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/expense/bookings/{id}/cancel
Cancel a booking
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
cancelled_bystring requiredcancellation_reasonstringrefund_amountnumber
Responses
200- Booking cancelled
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
404- Booking not found
422- Booking cannot be cancelled in its current state
GET /api/purple-suite/expense/expense_reports
List expense reports
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
ExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/expense_reports
Create a draft expense report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
ExpenseReport
idstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/expense_reports/{id}
Get an expense report by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/expense_reports/{id}
Delete an expense report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/expense_reports/{id}
Edit an expense report's details
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
ExpenseReport
idstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/expense/expense_reports/{id}/submit
Submit a draft expense report for approval
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
submitted_bystring — Defaults to employee_email if omitted
Responses
200- Report submitted
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Report not found
422- Report is not in draft status
POST /api/purple-suite/expense/expense_reports/{id}/approve
Approve a submitted expense report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
approved_bystring requiredapprover_notesstring
Responses
200- Report approved
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Report not found
422- Report is not in submitted status
POST /api/purple-suite/expense/expense_reports/{id}/reject
Reject a submitted expense report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
rejected_bystring requiredrejection_reasonstring required
Responses
200- Report rejected
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Report not found
422- Report is not in submitted status, or rejection_reason is missing
POST /api/purple-suite/expense/expense_reports/{id}/reimburse
Mark an approved expense report as paid out
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
reimbursement_methodstringreimbursement_referencestring
Responses
200- Report reimbursed
application/jsonExpenseReportidstringnamestring — User-facing title (e.g. 'May client trip')employee_emailstringtrip_idstring — Optional link to a Tripstatusstringtotal_amountnumber — Server-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignoredcurrencystringcost_centerstringproject_codestringapprover_emailstringsubmitted_atstringsubmitted_bystringapproved_atstringapproved_bystringapprover_notesstringrejected_atstringrejected_bystringrejection_reasonstringreimbursed_atstringreimbursement_methodstringreimbursement_referencestringcreatedAtstringupdatedAtstring
404- Report not found
422- Report is not in approved status
GET /api/purple-suite/expense/expense_items
List expense line items (receipts)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
ExpenseItemidstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/expense_items
Add an expense line item to a report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
ExpenseItem
idstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonExpenseItemidstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/expense_items/{id}
Get an expense line item by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonExpenseItemidstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/expense_items/{id}
Remove an expense line item from a report
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/expense_items/{id}
Edit an expense line item
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
ExpenseItem
idstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonExpenseItemidstringreport_idstringtrip_idstringcategorystringmerchantstringdescriptionstringtransaction_datestringamountnumbercurrencystringexchange_ratenumber — Rate to base currency (USD); 1.0 for domesticamount_in_base_currencynumberpayment_methodstringreceipt_urlstring — Link to stored receipt image or PDFstatusstringpolicy_violationstring — Set when status is flagged_policyis_billableboolean — Whether to bill back to a clientbillable_clientstringnotesstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/expense/expense_policies
List spend policy rules by expense category
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
ExpensePolicyidstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/expense_policies
Create a spend policy rule for a category
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
ExpensePolicy
idstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonExpensePolicyidstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/expense_policies/{id}
Get a spend policy rule by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonExpensePolicyidstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/expense_policies/{id}
Delete a spend policy rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/expense_policies/{id}
Edit a spend policy rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
ExpensePolicy
idstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonExpensePolicyidstringcategorystringdaily_limitnumber — Maximum spend per day for this category (null if not applicable)per_transaction_limitnumber — Maximum per single transaction (null if not applicable)requires_receipt_abovenumber — Dollar threshold above which a receipt is requiredrequires_pre_approval_abovenumber — Dollar threshold above which pre-approval is required (null if not applicable)allowed_payment_methodsarrayarray of:
one of:
"corporate_card","personal_card","cash","out_of_pocket"mileage_ratenumber — Per-distance reimbursement rate in the instance's base currency (e.g. IRS $0.67/mi for a US-primary instance, ₹12/km for IN); only relevant for category=mileagemileage_unitstring — Distance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileagecurrencystringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/expense/approvals
List approval requests (trips and expense reports)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Approvalidstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/approvals
Raise an approval request
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Approval
idstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonApprovalidstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/approvals/{id}
Get an approval request by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonApprovalidstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/approvals/{id}
Delete an approval request
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/approvals/{id}
Edit an approval request
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Approval
idstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonApprovalidstringtarget_typestring — The kind of record being approvedtarget_idstringrequester_emailstringapprover_emailstringstatusstringamountnumbercurrencystringrequested_atstringdecided_atstringdecision_notesstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/expense/airlines
List airline carriers (reference data)
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
Airlineidstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/airlines
Add an airline carrier
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Airline
idstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
Responses
201- Created
application/jsonAirlineidstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
GET /api/purple-suite/expense/airlines/{id}
Get an airline carrier by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonAirlineidstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/airlines/{id}
Delete an airline carrier
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/airlines/{id}
Edit an airline carrier
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Airline
idstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
Responses
200- Updated
application/jsonAirlineidstringcodestring — IATA carrier codenamestringalliancestring — Airline alliance, or 'None'hubsarray — Primary hub airport codesPrimary hub airport codesarray of:
string
logo_urlstringon_time_ratenumber — Historical on-time arrival rate (0–1)createdAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/expense/flights/search
Search bookable flights
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
origin | query | string | no | Departure airport code |
destination | query | string | no | Arrival airport code |
date | query | string | no | Departure date (YYYY-MM-DD) |
cabin | query | string | no | Only return flights offering this cabin with seats available |
max_price | query | number | no | Only return flights with a matching fare at or below this price |
limit | query | integer | no |
Responses
200- Matching flights
application/jsondataarrayarray of:
FlightA scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
totalinteger
GET /api/purple-suite/expense/flights
List the raw flight inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
FlightA scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/flights
Add a flight to the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Flight
idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonFlightA scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/flights/{id}
Get a flight by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonFlightA scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/flights/{id}
Delete a flight from the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/flights/{id}
Edit a flight in the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Flight
idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonFlightA scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.idstringflight_numberstringairline_codestringairline_namestringoriginstring — Departure airport codedestinationstring — Arrival airport codedeparture_datetimestringarrival_datetimestringduration_minutesintegerstopsinteger — Number of layovers (0 = nonstop)aircraftstringstatusstringfaresarray — Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.Available cabins with price and remaining seats. Booking a cabin decrements its seats_available.array of:
cabinstringpricenumberseats_availableintegerrefundableboolean
currencystringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/expense/flights/{id}/book
Book a flight
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
traveler_emailstring requiredcabinstring — Defaults to Economytrip_idstring — Optional. Trip to attach the booking to; if omitted, a new trip is auto-created from the flight.confirmation_numberstring — Optional; auto-generated if omitted
Responses
201- Flight booked
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
403- Traveler is not eligible for the requested cabin under fare-class policy
404- Flight not found
422- Cabin sold out, flight cancelled, or traveler_email missing
GET /api/purple-suite/expense/hotels/search
Search bookable hotels
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
city | query | string | no | Matches city name, city_code, or address (case-insensitive substring) |
max_nightly_rate | query | number | no | Only hotels at or below this nightly rate |
min_star_rating | query | number | no | Only hotels at or above this star rating |
limit | query | integer | no |
Responses
200- Matching hotels
application/jsondataarrayarray of:
HotelA bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
totalinteger
GET /api/purple-suite/expense/hotels
List the raw hotel inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
HotelA bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/hotels
Add a hotel to the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
Hotel
idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonHotelA bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/hotels/{id}
Get a hotel by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonHotelA bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/hotels/{id}
Delete a hotel from the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/hotels/{id}
Edit a hotel in the inventory
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
Hotel
idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonHotelA bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.idstringnamestringbrandstringcitystringcity_codestring — Airport/city code, aligned with trip destinationsaddressstringstar_ratingnumber — Star class, 1–5guest_ratingnumber — Average guest review score, 0–10nightly_ratenumber — Base nightly rate; room_types may overridecurrencystringrooms_availableinteger — Bookable rooms remaining; booking decrements thisdistance_to_city_center_kmnumberamenitiesarrayarray of:
string
room_typesarray — Optional room tiers with their own nightly ratesOptional room tiers with their own nightly ratesarray of:
namestringratenumberavailableinteger
statusstringcreatedAtstringupdatedAtstring
404- Not found
POST /api/purple-suite/expense/hotels/{id}/book
Book a hotel stay
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json — required
traveler_emailstring requiredcheck_instring requiredcheck_outstring requiredroom_typestring — Optional; uses the matching room_types rate, else the base nightly_ratetrip_idstring — Optional. Trip to attach the booking to; if omitted, a new trip is auto-created from the hotel stay.confirmation_numberstring — Optional; auto-generated if omitted
Responses
201- Hotel booked
application/jsonBookingidstringtrip_idstringbooking_typestringflight_idstring — Set when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)hotel_idstring — Set when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)vendorstring — Airline, hotel chain, car rental agency, or rail operatorconfirmation_numberstringstatusstringstart_datetimestringend_datetimestringoriginstring — Departure airport/city/pickup location (flights only)destinationstring — Arrival airport/city/dropoff locationfare_classstring — Cabin class (Economy, Business) or room type (Standard King)costnumbercurrencystringtraveler_emailstringnotesstringcancelled_atstringcancelled_bystringcancellation_reasonstringrefund_amountnumbercreatedAtstringupdatedAtstring
404- Hotel not found
422- No rooms available, invalid dates, or required fields missing
GET /api/purple-suite/expense/travel_policies
List fare-class (cabin) eligibility rules
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
TravelPolicyFare-class eligibility rule: the minimum seniority a traveler must hold to book a given cabin. Enforced by POST /flights/{id}/book — a traveler below the cabin's min_seniority is rejected with 403. Seniority is resolved from the employee's title (see HRIS).idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/travel_policies
Create a fare-class eligibility rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
TravelPolicy
idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonTravelPolicyFare-class eligibility rule: the minimum seniority a traveler must hold to book a given cabin. Enforced by POST /flights/{id}/book — a traveler below the cabin's min_seniority is rejected with 403. Seniority is resolved from the employee's title (see HRIS).idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/travel_policies/{id}
Get a fare-class eligibility rule by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonTravelPolicyFare-class eligibility rule: the minimum seniority a traveler must hold to book a given cabin. Enforced by POST /flights/{id}/book — a traveler below the cabin's min_seniority is rejected with 403. Seniority is resolved from the employee's title (see HRIS).idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/travel_policies/{id}
Delete a fare-class eligibility rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/travel_policies/{id}
Edit a fare-class eligibility rule
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
TravelPolicy
idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonTravelPolicyFare-class eligibility rule: the minimum seniority a traveler must hold to book a given cabin. Enforced by POST /flights/{id}/book — a traveler below the cabin's min_seniority is rejected with 403. Seniority is resolved from the employee's title (see HRIS).idstringcabin_classstring — The flight cabin this rule governsmin_senioritystring — Minimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.descriptionstringeffective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found
GET /api/purple-suite/expense/per_diem_rates/calculate
Calculate per diem for a destination
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
city_code | query | string | no | Destination airport/city code (preferred). Provide this or city. |
city | query | string | no | Destination city name (case-insensitive substring). Used if city_code is omitted. |
days | query | integer | no | Number of days to total the allowance over |
Responses
200- Per diem totals for the destination
application/jsoncitystringcity_codestringcountrystringcurrencystringdaysintegerlodging_ratenumbermie_ratenumbertotal_daily_ratenumberlodging_totalnumbermie_totalnumbertotalnumber
404- No per diem rate found for the destination
422- Missing city_code/city, or days is not a positive integer
GET /api/purple-suite/expense/per_diem_rates
List per diem allowance rates by destination
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
limit | query | integer | no | Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used. |
cursor | query | string | no | Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top. |
$filter | query | string | no | OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: "status eq 'active'"; "salary gt 50000 and department eq 'Engineering'"; "contains(tolower(name),'acme')"; "closeDate gt 2026-01-01". |
$select | query | string | no | Comma-separated list of fields to return, e.g. "id,firstName,email". Nested fields via slash ("pto_balance/vacation_days"). The id field is always included. |
$orderby | query | string | no | Sort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last. |
$search | query | string | no | Free-text, case-insensitive substring match across the resource's searchable fields. |
$top | query | integer | no | Maximum number of items to return (page size). |
$skip | query | integer | no | Number of matching items to skip before returning results (offset paging). Combine with $top. |
$count | query | boolean | no | When true, include @odata.count (total matches after $filter/$search, before paging) in the response. |
Responses
200- Paginated list
application/jsondataarrayarray of:
PerDiemRateDaily travel allowance for a destination: max lodging plus M&IE (meals & incidental expenses), GSA-style. Query the list, or use GET /per_diem_rates/calculate to total it over a number of days.idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
nextCursorstring — Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.totalinteger — Total documents in the collection.@odata.countinteger — Present only when $count=true: total matches after $filter/$search.@odata.truncatedboolean — Present and true only when the collection exceeded the in-memory query cap and results may be incomplete.
400- Invalid OData query (e.g. malformed $filter)
POST /api/purple-suite/expense/per_diem_rates
Create a per diem rate for a destination
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes |
Request body
application/json — required
PerDiemRate
idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
201- Created
application/jsonPerDiemRateDaily travel allowance for a destination: max lodging plus M&IE (meals & incidental expenses), GSA-style. Query the list, or use GET /per_diem_rates/calculate to total it over a number of days.idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
GET /api/purple-suite/expense/per_diem_rates/{id}
Get a per diem rate by id
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Found
application/jsonPerDiemRateDaily travel allowance for a destination: max lodging plus M&IE (meals & incidental expenses), GSA-style. Query the list, or use GET /per_diem_rates/calculate to total it over a number of days.idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found
DELETE /api/purple-suite/expense/per_diem_rates/{id}
Delete a per diem rate
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Responses
200- Deleted
application/jsonsuccessboolean
404- Not found
PATCH /api/purple-suite/expense/per_diem_rates/{id}
Edit a per diem rate
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
X-Instance-ID | header | string | yes | |
id | path | string | yes |
Request body
application/json
PerDiemRate
idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
Responses
200- Updated
application/jsonPerDiemRateDaily travel allowance for a destination: max lodging plus M&IE (meals & incidental expenses), GSA-style. Query the list, or use GET /per_diem_rates/calculate to total it over a number of days.idstringcitystringcity_codestring — Airport/city code, aligned with trip destinations and hotel city_codecountrystring — ISO country codelodging_ratenumber — Maximum reimbursable lodging per nightmie_ratenumber — Meals & incidental expenses allowance per daytotal_daily_ratenumber — lodging_rate + mie_rate — the full per diem per daycurrencystring — The instance's base currency. Rates for foreign destinations are FX-converted into it at seed time, so they compare directly with trip and expense amounts (which carry the same currency).effective_datestringnotesstringcreatedAtstringupdatedAtstring
404- Not found