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

Purple Expense — travel & expense management. Search bookable flight and hotel inventory, book trips, capture receipts, submit expense reports, and run approvals. Resembles tools like SAP Concur, Navan, and Expensify. --- **Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`.

Endpoints

GET /api/purple-suite/expense/trips

List business trips

A trip is the travel-request container in Purple Expense: traveler, dates, origin/destination, purpose, cost centre and approval state. Bookings hang off a trip via trip_id and an expense report can link to one via trip_id. Use this to find a traveler's upcoming or past travel; use bookings for the individual flight/hotel reservations. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Trip

    • id string
    • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
    • purpose stringBusiness reason for the trip
    • traveler_email string
    • start_date string
    • end_date string
    • origin stringDeparture airport or city code
    • destination stringPrimary destination airport or city code
    • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
    • is_international booleanTrue when the destination country differs from the traveler's home country.
    • status string
    • estimated_cost number
    • actual_cost numberPopulated once the trip is in_progress or completed
    • currency string
    • cost_center string
    • project_code string
    • approver_email string
    • approved_by string
    • approved_at string
    • approval_notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Opens a new trip in `planned` status for a traveler. Create the trip first when you want to group bookings and expenses under one itinerary — note that booking a flight or hotel without a trip_id auto-creates one for you.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/trips/{id}

Get a business trip by id

Returns one trip with its itinerary, cost fields and approval stamps (approved_by/at). Bookings and expense items are separate records that reference this trip's id.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/trips/{id}

Delete a business trip

DESTRUCTIVE and irreversible: removes the trip record. Bookings and expense items that referenced it are left pointing at a missing trip. Prefer setting status to `cancelled` unless the user really wants it gone.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/trips/{id}

Edit a business trip

Partial update of trip details (dates, destination, purpose, cost centre, estimated cost). For the planned → approved transition use the dedicated approve action instead of setting `status` here.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/expense/trips/{id}/approve

Pre-approve a trip

Manager pre-approval of the TRIP itself (the travel request), not of any spend. Transitions a trip from planned to approved and stamps approved_by/approved_at. Cannot approve a trip that is already approved, in_progress, completed or cancelled. Reimbursement of the money is a separate flow on expense reports.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • approved_by string required
  • approval_notes string

Responses

200
Trip approved

application/json

Trip

  • id string
  • name stringShort label for the trip (e.g. 'SF Sales Summit 2026')
  • purpose stringBusiness reason for the trip
  • traveler_email string
  • start_date string
  • end_date string
  • origin stringDeparture airport or city code
  • destination stringPrimary destination airport or city code
  • destination_country stringISO country of the destination (derived from the destination airport/city). Cross-reference with HRIS work authorizations to assess travel eligibility.
  • is_international booleanTrue when the destination country differs from the traveler's home country.
  • status string
  • estimated_cost number
  • actual_cost numberPopulated once the trip is in_progress or completed
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • approved_by string
  • approved_at string
  • approval_notes string
  • createdAt string
  • updatedAt string
404
Trip not found
422
Trip cannot be approved in its current state

GET /api/purple-suite/expense/bookings

List travel bookings (reservations)

A booking is one confirmed reservation — flight, hotel, car or rail — with vendor, confirmation number, cost and start/end datetimes, usually attached to a trip via trip_id and, when made from inventory, to the source flight_id/hotel_id. Bookings are what the traveler actually holds; flights and hotels are the bookable catalogue. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Booking

    • id string
    • trip_id string
    • booking_type string
    • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
    • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
    • vendor stringAirline, hotel chain, car rental agency, or rail operator
    • confirmation_number string
    • status string
    • start_datetime string
    • end_datetime string
    • origin stringDeparture airport/city/pickup location (flights only)
    • destination stringArrival airport/city/dropoff location
    • fare_class stringCabin class (Economy, Business) or room type (Standard King)
    • cost number
    • currency string
    • traveler_email string
    • notes string
    • cancelled_at string
    • cancelled_by string
    • cancellation_reason string
    • refund_amount number
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Creates a booking row for a reservation made outside the inventory (an existing confirmation number, a car or rail segment). It does NOT touch flight/hotel availability — to book from the catalogue and decrement seats/rooms, use the flight or hotel book action instead.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/bookings/{id}

Get a travel booking by id

Returns one reservation with its vendor, confirmation number, cost, cabin/room class and, if cancelled, the cancellation stamps and refund amount.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/bookings/{id}

Delete a travel booking

DESTRUCTIVE and irreversible: erases the reservation record entirely, with no refund and no release of inventory. Use the cancel action instead for a normal traveler-facing cancellation.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/bookings/{id}

Edit a travel booking

Partial update of reservation details (times, vendor, confirmation number, cost, traveler). To cancel, use the dedicated cancel action rather than setting `status` here — cancel is what writes the cancellation stamps and refund.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/expense/bookings/{id}/cancel

Cancel a booking

The traveler-facing way to cancel a reservation: marks the booking cancelled and records who cancelled it, why, and the refund amount. Cannot cancel a booking that is already cancelled or completed. Prefer this over deleting the booking, which erases the record and issues no refund.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • cancelled_by string required
  • cancellation_reason string
  • refund_amount number

Responses

200
Booking cancelled

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string
404
Booking not found
422
Booking cannot be cancelled in its current state

GET /api/purple-suite/expense/expense_reports

List expense reports

An expense report is the reimbursement envelope an employee submits: a named batch of expense items with an employee_email, optional trip_id, cost centre, approver and a status of draft → submitted → approved/rejected → reimbursed. `total_amount` is server-maintained from the attached items. Use this to answer 'what have I claimed / what is awaiting my approval'; use expense items for individual receipts. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    ExpenseReport

    • id string
    • name stringUser-facing title (e.g. 'May client trip')
    • employee_email string
    • trip_id stringOptional link to a Trip
    • status string
    • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
    • currency string
    • cost_center string
    • project_code string
    • approver_email string
    • submitted_at string
    • submitted_by string
    • approved_at string
    • approved_by string
    • approver_notes string
    • rejected_at string
    • rejected_by string
    • rejection_reason string
    • reimbursed_at string
    • reimbursement_method string
    • reimbursement_reference string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Opens a new report in `draft` for an employee, optionally linked to a trip. Add expense items to it (they carry report_id) and then call the submit action — do not set total_amount, it is computed.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/expense_reports/{id}

Get an expense report by id

Returns one report with its status, server-computed total and the full audit stamps for each transition (submitted_by/at, approved_by/at, rejection_reason, reimbursed_at, reimbursement_reference).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/expense_reports/{id}

Delete an expense report

DESTRUCTIVE and irreversible: removes the report, including any approval and reimbursement history. Expense items that referenced it are orphaned. Confirm with the user before calling.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/expense_reports/{id}

Edit an expense report's details

Partial update of report metadata (name, cost centre, project code, approver). Use the dedicated submit / approve / reject / reimburse actions to move `status` — they enforce the state machine and write the audit stamps. `total_amount` is server-maintained and ignored on input.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/expense/expense_reports/{id}/submit

Submit a draft expense report for approval

Step 1 of the reimbursement chain (submit → approve → reimburse). The EMPLOYEE sends their draft report to its approver: transitions draft → submitted and stamps submitted_by/at. Only draft reports can be submitted; use approve for the manager's decision.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • submitted_by stringDefaults to employee_email if omitted

Responses

200
Report submitted

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
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

Step 2 of the reimbursement chain: the APPROVER signs off on a submitted report, transitioning submitted → approved with approved_by/at and optional approver_notes. Approving does not pay the employee — call the reimburse action for that. Only submitted reports can be approved; use reject to decline one.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • approved_by string required
  • approver_notes string

Responses

200
Report approved

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
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

The approver's decline decision, the counterpart of approve: transitions submitted → rejected with rejected_by/at. `rejection_reason` is REQUIRED and is shown back to the employee. Only submitted reports can be rejected.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • rejected_by string required
  • rejection_reason string required

Responses

200
Report rejected

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
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

Final step of the reimbursement chain, normally performed by finance: transitions approved → reimbursed and records reimbursed_at, reimbursement_method (defaults to direct_deposit) and a payment reference. This is the money-moving step and is terminal — only approved reports qualify, and approve alone does not pay.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json

  • reimbursement_method string
  • reimbursement_reference string

Responses

200
Report reimbursed

application/json

ExpenseReport

  • id string
  • name stringUser-facing title (e.g. 'May client trip')
  • employee_email string
  • trip_id stringOptional link to a Trip
  • status string
  • total_amount numberServer-maintained running total of attached expense items; recomputed on every item create/update/delete. Values sent by clients are ignored
  • currency string
  • cost_center string
  • project_code string
  • approver_email string
  • submitted_at string
  • submitted_by string
  • approved_at string
  • approved_by string
  • approver_notes string
  • rejected_at string
  • rejected_by string
  • rejection_reason string
  • reimbursed_at string
  • reimbursement_method string
  • reimbursement_reference string
  • createdAt string
  • updatedAt string
404
Report not found
422
Report is not in approved status

GET /api/purple-suite/expense/expense_items

List expense line items (receipts)

An expense item is a single spend line — one receipt: merchant, category, transaction_date, amount (plus FX to base currency), payment method, receipt_url and a status that can be flagged_policy with a policy_violation note. Items attach to an expense report via report_id and optionally a trip via trip_id. Use this for individual charges; use expense reports for the submitted envelope. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    ExpenseItem

    • id string
    • report_id string
    • trip_id string
    • category string
    • merchant string
    • description string
    • transaction_date string
    • amount number
    • currency string
    • exchange_rate numberRate to base currency (USD); 1.0 for domestic
    • amount_in_base_currency number
    • payment_method string
    • receipt_url stringLink to stored receipt image or PDF
    • status string
    • policy_violation stringSet when status is flagged_policy
    • is_billable booleanWhether to bill back to a client
    • billable_client string
    • notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Records one charge against the report named in `report_id` (and optionally a trip). Creating an item recomputes the parent report's total_amount. Categories and limits are governed by the expense_policies collection.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

ExpenseItem

  • id string
  • report_id string
  • trip_id string
  • category string
  • merchant string
  • description string
  • transaction_date string
  • amount number
  • currency string
  • exchange_rate numberRate to base currency (USD); 1.0 for domestic
  • amount_in_base_currency number
  • payment_method string
  • receipt_url stringLink to stored receipt image or PDF
  • status string
  • policy_violation stringSet when status is flagged_policy
  • is_billable booleanWhether to bill back to a client
  • billable_client string
  • notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

ExpenseItem

  • id string
  • report_id string
  • trip_id string
  • category string
  • merchant string
  • description string
  • transaction_date string
  • amount number
  • currency string
  • exchange_rate numberRate to base currency (USD); 1.0 for domestic
  • amount_in_base_currency number
  • payment_method string
  • receipt_url stringLink to stored receipt image or PDF
  • status string
  • policy_violation stringSet when status is flagged_policy
  • is_billable booleanWhether to bill back to a client
  • billable_client string
  • notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/expense_items/{id}

Get an expense line item by id

Returns one spend line with its amount, currency conversion, receipt link, billability and any policy violation recorded against it.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

ExpenseItem

  • id string
  • report_id string
  • trip_id string
  • category string
  • merchant string
  • description string
  • transaction_date string
  • amount number
  • currency string
  • exchange_rate numberRate to base currency (USD); 1.0 for domestic
  • amount_in_base_currency number
  • payment_method string
  • receipt_url stringLink to stored receipt image or PDF
  • status string
  • policy_violation stringSet when status is flagged_policy
  • is_billable booleanWhether to bill back to a client
  • billable_client string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/expense_items/{id}

Remove an expense line item from a report

DESTRUCTIVE and irreversible: deletes the receipt line and recomputes the parent report's total_amount downward. There is no undo.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/expense_items/{id}

Edit an expense line item

Partial update of one charge (amount, category, merchant, receipt_url, billability). Changing the amount recomputes the parent report's total_amount.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

ExpenseItem

  • id string
  • report_id string
  • trip_id string
  • category string
  • merchant string
  • description string
  • transaction_date string
  • amount number
  • currency string
  • exchange_rate numberRate to base currency (USD); 1.0 for domestic
  • amount_in_base_currency number
  • payment_method string
  • receipt_url stringLink to stored receipt image or PDF
  • status string
  • policy_violation stringSet when status is flagged_policy
  • is_billable booleanWhether to bill back to a client
  • billable_client string
  • notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

ExpenseItem

  • id string
  • report_id string
  • trip_id string
  • category string
  • merchant string
  • description string
  • transaction_date string
  • amount number
  • currency string
  • exchange_rate numberRate to base currency (USD); 1.0 for domestic
  • amount_in_base_currency number
  • payment_method string
  • receipt_url stringLink to stored receipt image or PDF
  • status string
  • policy_violation stringSet when status is flagged_policy
  • is_billable booleanWhether to bill back to a client
  • billable_client string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/expense/expense_policies

List spend policy rules by expense category

An expense policy is the SPEND rule for one category (meals, lodging, mileage, …): daily_limit, per_transaction_limit, the receipt threshold, the pre-approval threshold, allowed payment methods and the mileage rate/unit. Read these to answer 'is this claim within policy' or 'what is the mileage rate'. Distinct from travel_policies, which govern flight cabin eligibility, and per_diem_rates, which are destination allowances. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    ExpensePolicy

    • id string
    • category string
    • daily_limit numberMaximum spend per day for this category (null if not applicable)
    • per_transaction_limit numberMaximum per single transaction (null if not applicable)
    • requires_receipt_above numberDollar threshold above which a receipt is required
    • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
    • allowed_payment_methods array

      array of:

      one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

    • mileage_rate numberPer-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=mileage
    • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
    • currency string
    • effective_date string
    • notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Defines the limits and receipt/pre-approval thresholds for one expense category. Administrative configuration — it changes what counts as a violation for future claims.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

ExpensePolicy

  • id string
  • category string
  • daily_limit numberMaximum spend per day for this category (null if not applicable)
  • per_transaction_limit numberMaximum per single transaction (null if not applicable)
  • requires_receipt_above numberDollar threshold above which a receipt is required
  • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
  • allowed_payment_methods array

    array of:

    one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

  • mileage_rate numberPer-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=mileage
  • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
  • currency string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

ExpensePolicy

  • id string
  • category string
  • daily_limit numberMaximum spend per day for this category (null if not applicable)
  • per_transaction_limit numberMaximum per single transaction (null if not applicable)
  • requires_receipt_above numberDollar threshold above which a receipt is required
  • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
  • allowed_payment_methods array

    array of:

    one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

  • mileage_rate numberPer-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=mileage
  • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
  • currency string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/expense_policies/{id}

Get a spend policy rule by id

Returns one category's limits, thresholds, allowed payment methods and effective date.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

ExpensePolicy

  • id string
  • category string
  • daily_limit numberMaximum spend per day for this category (null if not applicable)
  • per_transaction_limit numberMaximum per single transaction (null if not applicable)
  • requires_receipt_above numberDollar threshold above which a receipt is required
  • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
  • allowed_payment_methods array

    array of:

    one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

  • mileage_rate numberPer-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=mileage
  • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
  • currency string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/expense_policies/{id}

Delete a spend policy rule

DESTRUCTIVE and irreversible: removes the category's limits entirely, so that category becomes unconstrained. Confirm with the user before calling.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/expense_policies/{id}

Edit a spend policy rule

Partial update of a category's limits or thresholds. Administrative change that alters what is flagged as a policy violation going forward; it does not re-evaluate existing expense items.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

ExpensePolicy

  • id string
  • category string
  • daily_limit numberMaximum spend per day for this category (null if not applicable)
  • per_transaction_limit numberMaximum per single transaction (null if not applicable)
  • requires_receipt_above numberDollar threshold above which a receipt is required
  • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
  • allowed_payment_methods array

    array of:

    one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

  • mileage_rate numberPer-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=mileage
  • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
  • currency string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

ExpensePolicy

  • id string
  • category string
  • daily_limit numberMaximum spend per day for this category (null if not applicable)
  • per_transaction_limit numberMaximum per single transaction (null if not applicable)
  • requires_receipt_above numberDollar threshold above which a receipt is required
  • requires_pre_approval_above numberDollar threshold above which pre-approval is required (null if not applicable)
  • allowed_payment_methods array

    array of:

    one of: "corporate_card", "personal_card", "cash", "out_of_pocket"

  • mileage_rate numberPer-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=mileage
  • mileage_unit stringDistance unit the mileage_rate is per; set by the primary locale pack. Only present for category=mileage
  • currency string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/expense/approvals

List approval requests (trips and expense reports)

An approval is the routing/audit record for one decision: target_type (trip or expense_report) plus target_id, the requester, the assigned approver, amount, status and decision notes. Query it to build an approver's queue across both trips and reports. The decision itself is made with the trip approve or expense-report approve/reject actions, not by editing this record. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Approval

    • id string
    • target_type stringThe kind of record being approved
    • target_id string
    • requester_email string
    • approver_email string
    • status string
    • amount number
    • currency string
    • requested_at string
    • decided_at string
    • decision_notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Creates a routing record asking a named approver to decide on a trip or expense report. It records the request only; it does not itself change the target record's status.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Approval

  • id string
  • target_type stringThe kind of record being approved
  • target_id string
  • requester_email string
  • approver_email string
  • status string
  • amount number
  • currency string
  • requested_at string
  • decided_at string
  • decision_notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Approval

  • id string
  • target_type stringThe kind of record being approved
  • target_id string
  • requester_email string
  • approver_email string
  • status string
  • amount number
  • currency string
  • requested_at string
  • decided_at string
  • decision_notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/approvals/{id}

Get an approval request by id

Returns one approval record — what it targets, who must decide, the amount, and the decision and notes if made.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Approval

  • id string
  • target_type stringThe kind of record being approved
  • target_id string
  • requester_email string
  • approver_email string
  • status string
  • amount number
  • currency string
  • requested_at string
  • decided_at string
  • decision_notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/approvals/{id}

Delete an approval request

DESTRUCTIVE and irreversible: removes the approval record and its decision history. The target trip or report is left unchanged.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/approvals/{id}

Edit an approval request

Partial update of the routing record (reassign the approver, note a decision). Prefer the trip approve or expense-report approve/reject/reimburse actions to actually move the target through its state machine.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Approval

  • id string
  • target_type stringThe kind of record being approved
  • target_id string
  • requester_email string
  • approver_email string
  • status string
  • amount number
  • currency string
  • requested_at string
  • decided_at string
  • decision_notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Approval

  • id string
  • target_type stringThe kind of record being approved
  • target_id string
  • requester_email string
  • approver_email string
  • status string
  • amount number
  • currency string
  • requested_at string
  • decided_at string
  • decision_notes string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/expense/airlines

List airline carriers (reference data)

An airline is reference data about a carrier — IATA code, name, alliance, hub airports and historical on-time rate. Use it to resolve or describe the airline_code on a flight; it holds no schedules or availability, which live on flights. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Airline

    • id string
    • code stringIATA carrier code
    • name string
    • alliance stringAirline alliance, or 'None'
    • hubs arrayPrimary hub airport codes
      Primary hub airport codes

      array of:

      string

    • logo_url string
    • on_time_rate numberHistorical on-time arrival rate (0–1)
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Adds a carrier to the reference list (code, name, alliance, hubs). Seed/admin data — it does not create any bookable flights.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Airline

  • id string
  • code stringIATA carrier code
  • name string
  • alliance stringAirline alliance, or 'None'
  • hubs arrayPrimary hub airport codes
    Primary hub airport codes

    array of:

    string

  • logo_url string
  • on_time_rate numberHistorical on-time arrival rate (0–1)
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Airline

  • id string
  • code stringIATA carrier code
  • name string
  • alliance stringAirline alliance, or 'None'
  • hubs arrayPrimary hub airport codes
    Primary hub airport codes

    array of:

    string

  • logo_url string
  • on_time_rate numberHistorical on-time arrival rate (0–1)
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/airlines/{id}

Get an airline carrier by id

Returns one carrier's reference details: IATA code, name, alliance, hubs and on-time rate.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Airline

  • id string
  • code stringIATA carrier code
  • name string
  • alliance stringAirline alliance, or 'None'
  • hubs arrayPrimary hub airport codes
    Primary hub airport codes

    array of:

    string

  • logo_url string
  • on_time_rate numberHistorical on-time arrival rate (0–1)
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/airlines/{id}

Delete an airline carrier

DESTRUCTIVE and irreversible: removes the carrier from reference data. Flights keep their airline_code but can no longer be resolved to a carrier.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/airlines/{id}

Edit an airline carrier

Partial update of carrier reference data. Does not affect flights already referencing the carrier code.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Airline

  • id string
  • code stringIATA carrier code
  • name string
  • alliance stringAirline alliance, or 'None'
  • hubs arrayPrimary hub airport codes
    Primary hub airport codes

    array of:

    string

  • logo_url string
  • on_time_rate numberHistorical on-time arrival rate (0–1)
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Airline

  • id string
  • code stringIATA carrier code
  • name string
  • alliance stringAirline alliance, or 'None'
  • hubs arrayPrimary hub airport codes
    Primary hub airport codes

    array of:

    string

  • logo_url string
  • on_time_rate numberHistorical on-time arrival rate (0–1)
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/expense/flights/search

Search bookable flights

The shopping entry point for air travel — prefer this over listing flights. Filters the inventory by route, date, cabin and price, returns only flights with at least one matching fare that still has seats available, cheapest fare first. Read-only: pass a returned flight's id to the flight book action to actually reserve a seat.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
originquerystringnoDeparture airport code
destinationquerystringnoArrival airport code
datequerystringnoDeparture date (YYYY-MM-DD)
cabinquerystringnoOnly return flights offering this cabin with seats available
max_pricequerynumbernoOnly return flights with a matching fare at or below this price
limitqueryintegerno

Responses

200
Matching flights

application/json

  • data array

    array of:

    Flight

    A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
    • id string
    • flight_number string
    • airline_code string
    • airline_name string
    • origin stringDeparture airport code
    • destination stringArrival airport code
    • departure_datetime string
    • arrival_datetime string
    • duration_minutes integer
    • stops integerNumber of layovers (0 = nonstop)
    • aircraft string
    • status string
    • fares arrayAvailable 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:

      • cabin string
      • price number
      • seats_available integer
      • refundable boolean
    • currency string
    • createdAt string
    • updatedAt string
  • total integer

GET /api/purple-suite/expense/flights

List the raw flight inventory

A flight is one scheduled segment in the bookable catalogue: number, carrier, origin/destination, times, stops, status and a `fares` array of cabins with price and seats_available. This is the unfiltered catalogue dump — to shop for travel prefer the flight search operation, which filters by route/date/cabin/price and returns only flights with seats left, cheapest first. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Flight

    A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
    • id string
    • flight_number string
    • airline_code string
    • airline_name string
    • origin stringDeparture airport code
    • destination stringArrival airport code
    • departure_datetime string
    • arrival_datetime string
    • duration_minutes integer
    • stops integerNumber of layovers (0 = nonstop)
    • aircraft string
    • status string
    • fares arrayAvailable 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:

      • cabin string
      • price number
      • seats_available integer
      • refundable boolean
    • currency string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Seeds a new bookable segment with its cabins/fares. Inventory administration — this is not how a traveler books; use the flight book action.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Flight

A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
  • id string
  • flight_number string
  • airline_code string
  • airline_name string
  • origin stringDeparture airport code
  • destination stringArrival airport code
  • departure_datetime string
  • arrival_datetime string
  • duration_minutes integer
  • stops integerNumber of layovers (0 = nonstop)
  • aircraft string
  • status string
  • fares arrayAvailable 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:

    • cabin string
    • price number
    • seats_available integer
    • refundable boolean
  • currency string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Flight

A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
  • id string
  • flight_number string
  • airline_code string
  • airline_name string
  • origin stringDeparture airport code
  • destination stringArrival airport code
  • departure_datetime string
  • arrival_datetime string
  • duration_minutes integer
  • stops integerNumber of layovers (0 = nonstop)
  • aircraft string
  • status string
  • fares arrayAvailable 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:

    • cabin string
    • price number
    • seats_available integer
    • refundable boolean
  • currency string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/flights/{id}

Get a flight by id

Returns one segment with its schedule and live per-cabin fares/seats_available. Use before booking to confirm the cabin and price.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Flight

A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
  • id string
  • flight_number string
  • airline_code string
  • airline_name string
  • origin stringDeparture airport code
  • destination stringArrival airport code
  • departure_datetime string
  • arrival_datetime string
  • duration_minutes integer
  • stops integerNumber of layovers (0 = nonstop)
  • aircraft string
  • status string
  • fares arrayAvailable 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:

    • cabin string
    • price number
    • seats_available integer
    • refundable boolean
  • currency string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/flights/{id}

Delete a flight from the inventory

DESTRUCTIVE and irreversible: removes the segment from the catalogue. Existing bookings keep their flight_id but it no longer resolves.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/flights/{id}

Edit a flight in the inventory

Partial update of a segment's schedule, status or fares. Inventory administration — booking already decrements seats_available for you.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Flight

A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
  • id string
  • flight_number string
  • airline_code string
  • airline_name string
  • origin stringDeparture airport code
  • destination stringArrival airport code
  • departure_datetime string
  • arrival_datetime string
  • duration_minutes integer
  • stops integerNumber of layovers (0 = nonstop)
  • aircraft string
  • status string
  • fares arrayAvailable 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:

    • cabin string
    • price number
    • seats_available integer
    • refundable boolean
  • currency string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Flight

A scheduled flight in the bookable inventory. Search with GET /flights/search, then book with POST /flights/{id}/book.
  • id string
  • flight_number string
  • airline_code string
  • airline_name string
  • origin stringDeparture airport code
  • destination stringArrival airport code
  • departure_datetime string
  • arrival_datetime string
  • duration_minutes integer
  • stops integerNumber of layovers (0 = nonstop)
  • aircraft string
  • status string
  • fares arrayAvailable 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:

    • cabin string
    • price number
    • seats_available integer
    • refundable boolean
  • currency string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/expense/flights/{id}/book

Book a flight

Reserves a seat in the given cabin and creates a confirmed booking that links back to this flight via flight_id. Decrements the cabin's seats_available. Fails if the cabin is sold out or the flight is cancelled. Premium cabins are subject to fare-class policy (see the travel_policies collection): if the cabin's min_seniority exceeds the traveler's level, the booking is rejected with 403. If no trip_id is supplied, a trip is auto-created from the flight's route/dates and the booking is attached to it.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • traveler_email string required
  • cabin stringDefaults to Economy
  • trip_id stringOptional. Trip to attach the booking to; if omitted, a new trip is auto-created from the flight.
  • confirmation_number stringOptional; auto-generated if omitted

Responses

201
Flight booked

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string
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

The shopping entry point for lodging — prefer this over listing hotels. Filters the inventory by city, nightly rate and star rating, returns only active properties with rooms available, cheapest first. Read-only: pass a returned hotel's id to the hotel book action to actually reserve a room.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
cityquerystringnoMatches city name, city_code, or address (case-insensitive substring)
max_nightly_ratequerynumbernoOnly hotels at or below this nightly rate
min_star_ratingquerynumbernoOnly hotels at or above this star rating
limitqueryintegerno

Responses

200
Matching hotels

application/json

  • data array

    array of:

    Hotel

    A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
    • id string
    • name string
    • brand string
    • city string
    • city_code stringAirport/city code, aligned with trip destinations
    • address string
    • star_rating numberStar class, 1–5
    • guest_rating numberAverage guest review score, 0–10
    • nightly_rate numberBase nightly rate; room_types may override
    • currency string
    • rooms_available integerBookable rooms remaining; booking decrements this
    • distance_to_city_center_km number
    • amenities array

      array of:

      string

    • room_types arrayOptional room tiers with their own nightly rates
      Optional room tiers with their own nightly rates

      array of:

      • name string
      • rate number
      • available integer
    • status string
    • createdAt string
    • updatedAt string
  • total integer

GET /api/purple-suite/expense/hotels

List the raw hotel inventory

A hotel is one bookable property: name, brand, city/city_code, star and guest ratings, nightly_rate, amenities, room_types and rooms_available. This is the unfiltered catalogue dump — to shop for lodging prefer the hotel search operation, which filters by city, rate and star rating and returns only active properties with rooms left. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    Hotel

    A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
    • id string
    • name string
    • brand string
    • city string
    • city_code stringAirport/city code, aligned with trip destinations
    • address string
    • star_rating numberStar class, 1–5
    • guest_rating numberAverage guest review score, 0–10
    • nightly_rate numberBase nightly rate; room_types may override
    • currency string
    • rooms_available integerBookable rooms remaining; booking decrements this
    • distance_to_city_center_km number
    • amenities array

      array of:

      string

    • room_types arrayOptional room tiers with their own nightly rates
      Optional room tiers with their own nightly rates

      array of:

      • name string
      • rate number
      • available integer
    • status string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Seeds a new bookable property with its rates and room types. Inventory administration — travelers book via the hotel book action.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

Hotel

A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
  • id string
  • name string
  • brand string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations
  • address string
  • star_rating numberStar class, 1–5
  • guest_rating numberAverage guest review score, 0–10
  • nightly_rate numberBase nightly rate; room_types may override
  • currency string
  • rooms_available integerBookable rooms remaining; booking decrements this
  • distance_to_city_center_km number
  • amenities array

    array of:

    string

  • room_types arrayOptional room tiers with their own nightly rates
    Optional room tiers with their own nightly rates

    array of:

    • name string
    • rate number
    • available integer
  • status string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

Hotel

A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
  • id string
  • name string
  • brand string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations
  • address string
  • star_rating numberStar class, 1–5
  • guest_rating numberAverage guest review score, 0–10
  • nightly_rate numberBase nightly rate; room_types may override
  • currency string
  • rooms_available integerBookable rooms remaining; booking decrements this
  • distance_to_city_center_km number
  • amenities array

    array of:

    string

  • room_types arrayOptional room tiers with their own nightly rates
    Optional room tiers with their own nightly rates

    array of:

    • name string
    • rate number
    • available integer
  • status string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/hotels/{id}

Get a hotel by id

Returns one property with its rates, amenities, room tiers and current rooms_available. Use before booking to confirm rate and availability.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

Hotel

A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
  • id string
  • name string
  • brand string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations
  • address string
  • star_rating numberStar class, 1–5
  • guest_rating numberAverage guest review score, 0–10
  • nightly_rate numberBase nightly rate; room_types may override
  • currency string
  • rooms_available integerBookable rooms remaining; booking decrements this
  • distance_to_city_center_km number
  • amenities array

    array of:

    string

  • room_types arrayOptional room tiers with their own nightly rates
    Optional room tiers with their own nightly rates

    array of:

    • name string
    • rate number
    • available integer
  • status string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/hotels/{id}

Delete a hotel from the inventory

DESTRUCTIVE and irreversible: removes the property from the catalogue. Existing bookings keep their hotel_id but it no longer resolves.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/hotels/{id}

Edit a hotel in the inventory

Partial update of a property's rate, status, amenities or room types. Inventory administration — booking already decrements rooms_available.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

Hotel

A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
  • id string
  • name string
  • brand string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations
  • address string
  • star_rating numberStar class, 1–5
  • guest_rating numberAverage guest review score, 0–10
  • nightly_rate numberBase nightly rate; room_types may override
  • currency string
  • rooms_available integerBookable rooms remaining; booking decrements this
  • distance_to_city_center_km number
  • amenities array

    array of:

    string

  • room_types arrayOptional room tiers with their own nightly rates
    Optional room tiers with their own nightly rates

    array of:

    • name string
    • rate number
    • available integer
  • status string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

Hotel

A bookable hotel in the inventory. Search with GET /hotels/search, then book with POST /hotels/{id}/book.
  • id string
  • name string
  • brand string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations
  • address string
  • star_rating numberStar class, 1–5
  • guest_rating numberAverage guest review score, 0–10
  • nightly_rate numberBase nightly rate; room_types may override
  • currency string
  • rooms_available integerBookable rooms remaining; booking decrements this
  • distance_to_city_center_km number
  • amenities array

    array of:

    string

  • room_types arrayOptional room tiers with their own nightly rates
    Optional room tiers with their own nightly rates

    array of:

    • name string
    • rate number
    • available integer
  • status string
  • createdAt string
  • updatedAt string
404
Not found

POST /api/purple-suite/expense/hotels/{id}/book

Book a hotel stay

Reserves a room for the given dates and creates a confirmed booking that links back to this hotel via hotel_id. Cost is the nightly rate × nights. Decrements rooms_available. If no trip_id is supplied, a trip is auto-created from the hotel's city/dates and the booking is attached to it.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

application/json — required

  • traveler_email string required
  • check_in string required
  • check_out string required
  • room_type stringOptional; uses the matching room_types rate, else the base nightly_rate
  • trip_id stringOptional. Trip to attach the booking to; if omitted, a new trip is auto-created from the hotel stay.
  • confirmation_number stringOptional; auto-generated if omitted

Responses

201
Hotel booked

application/json

Booking

  • id string
  • trip_id string
  • booking_type string
  • flight_id stringSet when this booking was made from a flight in the searchable inventory (see POST /flights/{id}/book)
  • hotel_id stringSet when this booking was made from a hotel in the searchable inventory (see POST /hotels/{id}/book)
  • vendor stringAirline, hotel chain, car rental agency, or rail operator
  • confirmation_number string
  • status string
  • start_datetime string
  • end_datetime string
  • origin stringDeparture airport/city/pickup location (flights only)
  • destination stringArrival airport/city/dropoff location
  • fare_class stringCabin class (Economy, Business) or room type (Standard King)
  • cost number
  • currency string
  • traveler_email string
  • notes string
  • cancelled_at string
  • cancelled_by string
  • cancellation_reason string
  • refund_amount number
  • createdAt string
  • updatedAt string
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

A travel policy is a cabin-eligibility rule: the minimum seniority a traveler must hold to book a given cabin_class. It is enforced at booking time — the flight book action rejects an under-ranked traveler with 403. Read it to answer 'am I allowed to fly business'. Distinct from expense_policies (spend limits per category). Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    TravelPolicy

    Fare-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).
    • id string
    • cabin_class stringThe flight cabin this rule governs
    • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
    • description string
    • effective_date string
    • notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Sets the minimum seniority required for a cabin class. Administrative — it takes effect on future flight bookings.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

TravelPolicy

Fare-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).
  • id string
  • cabin_class stringThe flight cabin this rule governs
  • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
  • description string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

TravelPolicy

Fare-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).
  • id string
  • cabin_class stringThe flight cabin this rule governs
  • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
  • description string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/travel_policies/{id}

Get a fare-class eligibility rule by id

Returns one cabin rule: the cabin_class it governs, its min_seniority and effective date.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

TravelPolicy

Fare-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).
  • id string
  • cabin_class stringThe flight cabin this rule governs
  • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
  • description string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/travel_policies/{id}

Delete a fare-class eligibility rule

DESTRUCTIVE and irreversible: removes the restriction, leaving that cabin bookable by anyone. Confirm with the user before calling.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/travel_policies/{id}

Edit a fare-class eligibility rule

Partial update of a cabin's min_seniority or description. Changes who can book that cabin from now on; existing bookings are untouched.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

TravelPolicy

Fare-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).
  • id string
  • cabin_class stringThe flight cabin this rule governs
  • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
  • description string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

TravelPolicy

Fare-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).
  • id string
  • cabin_class stringThe flight cabin this rule governs
  • min_seniority stringMinimum seniority level permitted to book this cabin. Levels, lowest to highest: Individual Contributor, Manager, Senior Manager, Director, VP.
  • description string
  • effective_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

GET /api/purple-suite/expense/per_diem_rates/calculate

Calculate per diem for a destination

Looks up a city's per diem rate (by city_code or city name) and totals it over a number of days, returning lodging, M&IE and combined amounts. Use this whenever the question is 'how much allowance for N days in X'; use the per diem rates list only to browse the underlying rate table. Read-only — it creates nothing.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
city_codequerystringnoDestination airport/city code (preferred). Provide this or city.
cityquerystringnoDestination city name (case-insensitive substring). Used if city_code is omitted.
daysqueryintegernoNumber of days to total the allowance over

Responses

200
Per diem totals for the destination

application/json

  • city string
  • city_code string
  • country string
  • currency string
  • days integer
  • lodging_rate number
  • mie_rate number
  • total_daily_rate number
  • lodging_total number
  • mie_total number
  • total number
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

A per diem rate is a GSA-style daily travel allowance for a city: lodging_rate plus mie_rate (meals & incidentals) and their total, in the instance's base currency. Use this to browse or compare destinations; to total an allowance over a stay use the per diem calculate operation instead. Server-side query: $filter/$select/$orderby/$search/$top/$skip/$count (MCP clients pass these as _filter, _top, … because $ is not a legal tool-input key).

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
limitqueryintegernoLegacy page size (default 50). Prefer $top. Ignored when $top/$skip are used.
cursorquerystringnoLegacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top.
$filterquerystringnoOData 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".
$selectquerystringnoComma-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.
$orderbyquerystringnoSort clause(s), comma-separated, each "field [asc|desc]" (asc is the default). Example: "createdAt desc, lastName asc". Null values sort last.
$searchquerystringnoFree-text, case-insensitive substring match across the resource's searchable fields.
$topqueryintegernoMaximum number of items to return (page size).
$skipqueryintegernoNumber of matching items to skip before returning results (offset paging). Combine with $top.
$countquerybooleannoWhen true, include @odata.count (total matches after $filter/$search, before paging) in the response.

Responses

200
Paginated list

application/json

  • data array

    array of:

    PerDiemRate

    Daily 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.
    • id string
    • city string
    • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
    • country stringISO country code
    • lodging_rate numberMaximum reimbursable lodging per night
    • mie_rate numberMeals & incidental expenses allowance per day
    • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
    • currency stringThe 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_date string
    • notes string
    • createdAt string
    • updatedAt string
  • nextCursor stringCursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor.
  • total integerTotal documents in the collection.
  • @odata.count integerPresent only when $count=true: total matches after $filter/$search.
  • @odata.truncated booleanPresent 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

Adds a city's lodging and M&IE allowance. Administrative rate-table maintenance, not a traveler action.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes

Request body

application/json — required

PerDiemRate

Daily 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.
  • id string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
  • country stringISO country code
  • lodging_rate numberMaximum reimbursable lodging per night
  • mie_rate numberMeals & incidental expenses allowance per day
  • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
  • currency stringThe 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_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

201
Created

application/json

PerDiemRate

Daily 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.
  • id string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
  • country stringISO country code
  • lodging_rate numberMaximum reimbursable lodging per night
  • mie_rate numberMeals & incidental expenses allowance per day
  • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
  • currency stringThe 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_date string
  • notes string
  • createdAt string
  • updatedAt string

GET /api/purple-suite/expense/per_diem_rates/{id}

Get a per diem rate by id

Returns one destination's lodging, M&IE and combined daily allowance with its effective date.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Found

application/json

PerDiemRate

Daily 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.
  • id string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
  • country stringISO country code
  • lodging_rate numberMaximum reimbursable lodging per night
  • mie_rate numberMeals & incidental expenses allowance per day
  • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
  • currency stringThe 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_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found

DELETE /api/purple-suite/expense/per_diem_rates/{id}

Delete a per diem rate

DESTRUCTIVE and irreversible: removes the destination's allowance, after which per diem calculation for that city will not resolve.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Responses

200
Deleted

application/json

  • success boolean
404
Not found

PATCH /api/purple-suite/expense/per_diem_rates/{id}

Edit a per diem rate

Partial update of a destination's lodging/M&IE allowance. Affects future per diem calculations.

Parameters

NameInTypeRequiredDescription
X-Instance-IDheaderstringyes
idpathstringyes

Request body

Fields to update (partial)

application/json

PerDiemRate

Daily 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.
  • id string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
  • country stringISO country code
  • lodging_rate numberMaximum reimbursable lodging per night
  • mie_rate numberMeals & incidental expenses allowance per day
  • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
  • currency stringThe 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_date string
  • notes string
  • createdAt string
  • updatedAt string

Responses

200
Updated

application/json

PerDiemRate

Daily 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.
  • id string
  • city string
  • city_code stringAirport/city code, aligned with trip destinations and hotel city_code
  • country stringISO country code
  • lodging_rate numberMaximum reimbursable lodging per night
  • mie_rate numberMeals & incidental expenses allowance per day
  • total_daily_rate numberlodging_rate + mie_rate — the full per diem per day
  • currency stringThe 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_date string
  • notes string
  • createdAt string
  • updatedAt string
404
Not found