Skip to main content

Overview

Once transactions are in the system, the goal is to make it as easy as possible for employees to collect receipts, fill in descriptions, and get their expenses approved. Receipt collection is the first step—employees can upload receipts directly, forward them by email, or have them auto-matched to a transaction. As soon as a receipt is matched to a transaction, Tesouro auto-fills the accounting fields, so by the time the employee opens the expense it’s largely ready to submit. From there the expense moves through the approval flow and is categorized and ready for accounting export.

Receipt collection

The first step is for the employee to upload receipts, either:
  • Direct link: Upload the receipt and immediately link it to a specific transaction by sending PATCH /receipts/{receipt_id} with the transaction_id. Use this when the employee has the receipt in hand and knows which transaction it belongs to.
  • Auto-matching: Upload the receipt (via file upload, or email forwarding) without specifying a transaction. The auto-matching engine finds the right transaction based on amount, merchant name, and timestamp. See Receipts & OCR for upload paths and matching details.
To directly link an already-uploaded receipt to a transaction:
Once a receipt is matched — automatically or manually — Tesouro fires an enrichment step that pre-fills two fields on the transaction:
  • ledger_account_id: Tesouro compares the receipt content against the organization’s ledger accounts and selects the best match. Sets source_of_data.ledger_account_id to "generated".
  • description: If the transaction has no description, the AI generates a concise memo (e.g. "Dinner in NYC" or "Taxi ride"). Sets source_of_data.description to "generated". If it can’t produce a confident summary, the field is left empty.
The employee sees these values pre-filled when they open the expense and can override either before submitting. If OCR is still processing when the receipt is linked, enrichment fires after OCR completes.
Auto-categorization requires at least one ledger account to be configured. See Configure accounting fields.

Submit for approval

Call POST /transactions/{id}/submit to move the transaction from new to approve_in_progress. Tesouro enforces all configured requirements before the status changes — if any required fields are missing, the call returns a list of errors and the transaction stays in new:
If receipt_id is required and missing:
The employee should resolve all missing requirements before submitting. A successful submit returns the updated transaction with expense_status: "approve_in_progress":
After submit, Tesouro evaluates the active approval policies and, if one matches, sets applied_policy_id and notifies the designated approvers.

Submit without a receipt

If an employee can’t provide a receipt, they can bypass receipt validation by setting bypass_receipt_validation: true and supplying a no_receipt_reason. This records the exception for audit purposes:
The no_receipt_reason is stored on the transaction and visible to approvers. The has_validation_errors field on the response will be true to flag that the submission bypassed a requirement. See POST /transactions/{id}/submit for the full API reference.

Approval

Policy-driven approval

When a transaction is submitted, Tesouro runs it against the active approval policies. If a policy matches:
  • applied_policy_id is set on the transaction to identify which policy is running.
  • The policy’s script determines who must approve. This could be a specific user list, any user with a given role, or the employee’s reporting manager.
If no policy matches, the transaction stays in approve_in_progress until it is force-approved or rejected by an admin.

List pending approval requests

The designated approver can fetch their outstanding requests:
The roles field and role-based approval_type values are being refactored. Treat role-related fields as subject to change.

Approve the request

Once the required number of approvers have approved, the transaction moves to expense_status: "approved". Use GET /transactions/{id} to confirm the final state.

Force approve

POST /transactions/{id}/approve bypasses the normal policy-driven flow. Use it in two situations:
  • No policy matched: The transaction is in approve_in_progress but no policy applied. Call this to approve it directly.
  • Company admin override: A policy is running but a Company admin needs to approve immediately, regardless of the policy’s designated approvers.
The response returns the updated transaction with expense_status: "approved".

Reject a transaction

A transaction can be rejected in two ways, mirroring the two approval paths:
  • Via the approval request: The designated approver rejects the pending approval request. Use this in the normal policy-driven flow.
  • Force reject: An admin rejects the transaction directly, bypassing or overriding the policy. Use this when no policy applied or an admin needs to override.

Reject via approval request

Force reject

In both cases, reject_reason is stored on the transaction and visible to the employee. A rejected transaction is still accessible in the system, but the employee must reopen it (POST /transactions/{id}/reopen) before it can be submitted again. See POST /transactions/{id}/reject and POST /approval-requests/{id}/reject for the full API reference.

Accounting field auto-population

Transactions carry a source_of_data JSON object that records how each field was set. Fields like ledger_account_id, description, receipt_id, location_id, and department_id are all auto-populated but overridable — employees and admins can update any of them, and the entry in source_of_data updates to "user" to reflect that.

source_of_data on transactions

Each entry in the source_of_data object takes one of these values: location_id and department_id are auto-assigned from the employee’s user profile when the transaction is created. When an employee edits only one field, the other fields’ source_of_data entries are preserved — only the changed field’s origin updates to "user".

source_of_data on receipts

Receipts have their own source_of_data field that records how the receipt’s data was captured — separate from the transaction tracking above: When the AI auto-matching engine links a receipt to a transaction, it sets source_of_data.receipt_id to "automatched" on the transaction. A manual match sets it to "user". This distinction is shown in the expense management UI so employees can see at a glance whether a receipt was attached automatically or still needs their attention.