Guides

Expenses & Documents

Export expense reports to accounting, approve or reject them, and read document metadata, signers and files through the API, with the visibility rules that apply.

This guide covers expense reports and documents. Every parameter and field is in the API Reference.

Scopes for this area

TaskScopeLowest owner role
Read expense reportsexpenses:readEmployee
Approve and reject expense reportsexpenses:approveDepartment Head
Read document metadata and signersdocuments:readEmployee
Download document filesdocuments.content:readAdministrator

Expense reports

The expense endpoints need expenses switched on for the organization. While it is off they answer 403 MODULE_DISABLED, and no expense events are sent by webhook.

Whose reports a token can see

  • An Employee's token lists the reports they submitted.
  • A Department Head's token lists reports submitted by people in the departments they head or manage.
  • Administrators and Executives list every report in the organization.

GET /expense-reports/{id} also opens a report the owner is the assigned approver of. A report the owner cannot see answers 404 NOT_FOUND. Filters narrow the view and never widen it.

Export approved reports

curl "https://app.bookyourpto.com/api/v1/expense-reports?status=APPROVED&from=2026-09-01&to=2026-09-30&limit=100" \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN"

The list returns each report's header: number, title, submitter, approver, dates, status and totals (total_expenses, total_per_diems, total_mileage, total_tax, grand_total, currency), with counts of line items, per diems, mileage entries and receipts. Results are newest first. Filter with status, report_type (REIMBURSEMENT or COMPANY_CARD), user_id, and from / to, which match the trip's departure and return days.

For the lines, read each report:

curl "https://app.bookyourpto.com/api/v1/expense-reports/cmexp0000000000000000042" \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN"
{
  "data": {
    "id": "cmexp0000000000000000042",
    "report_number": "EXP-2026-0042",
    "status": "APPROVED",
    "currency": "CAD",
    "grand_total": 605.58,
    "line_items": [
      { "id": "cmeli0000000000000000001", "date": "2026-09-14", "vendor": "Harbourfront Suites", "expense_type": "ACCOMMODATION", "amount_before_tax": 412, "gst": 20.6, "total": 432.6, "receipt_id": "cmerc0000000000000000001", "...": "more fields" }
    ],
    "per_diems": [ { "date": "2026-09-15", "per_diem_type": "FULL_DAY", "rate": 112.5, "total": 112.5, "...": "more fields" } ],
    "mileage": [ { "date": "2026-09-16", "distance": 84, "unit": "KILOMETERS", "rate_per_unit": 0.72, "total": 60.48, "...": "more fields" } ],
    "receipts": [ { "id": "cmerc0000000000000000001", "file_name": "harbourfront-folio.pdf", "mime_type": "application/pdf", "size_bytes": 184220 } ],
    "...": "the header fields"
  },
  "meta": { "request_id": "..." }
}
  • Amounts are numbers in the currency named next to them; each line also has total_in_base_currency for the organization's currency.
  • Line dates are calendar days (YYYY-MM-DD).
  • receipts lists each receipt's metadata. Receipt images and files are not available through the API.

To pick up reports as they are approved or paid, rather than polling, subscribe to expense_report.approved and expense_report.paid (see Events).

Reference: List expense reports, Get an expense report.

Approve or reject a report

curl -X POST https://app.bookyourpto.com/api/v1/expense-reports/cmexp0000000000000000042/reject \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Hotel folio is missing the second night." }'
  • /approve takes optional notes; /reject requires a reason. Both are shown to the submitter, who is notified.
  • Only a report awaiting a decision (SUBMITTED or PENDING_APPROVAL) can be decided. Any other state answers 409 CONFLICT.
  • Administrators and Executives decide any report; a Department Head only reports from their department scope. A decision the owner may not make answers 403 FORBIDDEN.
  • Nobody decides their own report through the API, including Administrators and Executives, who can in the app. A token is automation acting for its owner, and approving the owner's own spending automatically would defeat the separation of duties the approval exists for.
  • The events are expense_report.approved and expense_report.rejected. Marking a report paid is done in the app, and raises expense_report.paid.

Reference: Approve, Reject.

Documents

Whose documents a token can see

The token sees what its owner's document list shows in the app:

  • Administrators and Executives see every document in the organization.
  • A Department Head sees their own documents and documents assigned to them for signature, plus documents they sent to people in their department scope.
  • An Employee sees their own documents and documents assigned to them for signature.
  • In sequential signing, a signer whose turn has not come yet does not see the document.

A document the owner cannot see answers 404 NOT_FOUND.

List documents and their signing status

curl "https://app.bookyourpto.com/api/v1/documents?signature_status=PENDING" \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN"
  • Filter with signature_status (NOT_REQUIRED, PENDING, SIGNED, DECLINED, EXPIRED), category and user_id (documents in that person's folder: owned by them or assigned to them to sign). Results are newest first.
  • Per-person copies of onboarding documents are left out unless you add include_copies=true.
  • GET /documents/{id} adds signers: each signer's status, signing order, due date and when they viewed, signed or declined.

Subscribe to document.signed and document.declined to follow signing without polling. document.signed is sent each time a signer signs; the document's signature_status becomes SIGNED when the last signer has signed.

Reference: Documents.

Download a file

curl "https://app.bookyourpto.com/api/v1/documents/cmdoc0000000000000000007/content" \
  -H "Authorization: Bearer $BOOKYOURPTO_TOKEN" \
  | jq -r '.data.content_base64' | base64 --decode > employment-agreement.pdf
  • Needs documents.content:read, which only Administrator and Executive owners can hold, and the document must be visible to the owner.
  • The file comes back base64 encoded in the JSON envelope, with file_name, mime_type and size_bytes. Files up to 10 MB can be downloaded; larger files answer 422 VALIDATION_FAILED and must be downloaded in the app.
  • Each download counts as 10 requests against the rate limits.
  • Every download is written to the audit log and the sensitive-data access log, with the token that made it.

Reference: Download a document.

Gotchas

  • Signing is not available through the API. A signature is a legal act bound to a person, so documents are signed in the app.
  • Expense reports are read-only apart from decisions. Reports, receipts and payments are created and changed in the app.
  • Every page of an expense or document list is recorded as an export in the audit log, with the record count.
  • Decode files as bytes. Write the decoded content_base64 straight to a file; do not pass it through a text encoding.