Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.tesouro.com/llms.txt

Use this file to discover all available pages before exploring further.

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:
curl -X PATCH 'https://api.sandbox.tesouro.com/v1/receipts/e5ea872b-a3d6-452b-b1d3-7b4e30b8ce2b' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{"transaction_id": "792c0577-3adf-4643-9570-2b641a5b04f2"}'
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:
curl -X POST 'https://api.sandbox.tesouro.com/v1/transactions/792c0577-3adf-4643-9570-2b641a5b04f2/submit' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{}'
If receipt_id is required and missing:
{
  "error": {
    "message": {
      "validation_errors": [
        {"field": "receipt_id", "message": "Field is required"}
      ]
    }
  }
}
The employee should resolve all missing requirements before submitting. A successful submit returns the updated transaction with expense_status: "approve_in_progress":
{
  "id": "792c0577-3adf-4643-9570-2b641a5b04f2",
  "expense_status": "approve_in_progress",
  "applied_policy_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "receipt_id": "e5ea872b-a3d6-452b-b1d3-7b4e30b8ce2b",
  "description": "Dinner in NYC",
  "ledger_account_id": "69e70421-23fd-4c6c-bcb5-ea3794373d44",
  "source_of_data": {
    "receipt_id": "automatched",
    "ledger_account_id": "generated",
    "description": "generated",
    "location_id": "automatched",
    "department_id": null
  }
}
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:
curl -X POST 'https://api.sandbox.tesouro.com/v1/transactions/792c0577-3adf-4643-9570-2b641a5b04f2/submit' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{
    "bypass_receipt_validation": true,
    "no_receipt_reason": "Receipt was lost. Purchase was a team lunch on May 22."
  }'
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:
curl -X GET 'https://api.sandbox.tesouro.com/v1/approval-requests?object_type=transaction&status=waiting' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23'
{
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "object_type": "transaction",
      "object_id": "792c0577-3adf-4643-9570-2b641a5b04f2",
      "status": "waiting",
      "approval_type": "user_ids",
      "required_approval_count": 1,
      "user_ids": ["FINANCE_MANAGER_USER_ID"],
      "roles": [],
      "submitted_by": "66d530d1-29b8-4c4f-93f5-fd43b3dc4b55",
      "approved_by": [],
      "rejected_by": null,
      "reject_reason": null,
      "created_by": null,
      "created_at": "2026-05-22T12:01:00.000000+00:00",
      "updated_at": "2026-05-22T12:01:00.000000+00:00"
    }
  ],
  "prev_pagination_token": null,
  "next_pagination_token": null
}
The roles field and role-based approval_type values are being refactored. Treat role-related fields as subject to change.

Approve the request

curl -X POST 'https://api.sandbox.tesouro.com/v1/approval-requests/a1b2c3d4-e5f6-7890-abcd-ef1234567890/approve' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23'
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.
  • Admin override: A policy is running but an admin needs to approve immediately, regardless of the policy’s designated approvers.
curl -X POST 'https://api.sandbox.tesouro.com/v1/transactions/792c0577-3adf-4643-9570-2b641a5b04f2/approve' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json'
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

curl -X POST 'https://api.sandbox.tesouro.com/v1/approval-requests/a1b2c3d4-e5f6-7890-abcd-ef1234567890/reject' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{"reject_reason": "Receipt is required for travel expenses over $20. Please attach the taxi receipt."}'

Force reject

curl -X POST 'https://api.sandbox.tesouro.com/v1/transactions/83724fa0-ca5c-4f4e-a2eb-44c8d1d1f1c2/reject' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{"reject_reason": "Receipt is required for travel expenses over $20. Please attach the taxi receipt."}'
{
  "id": "83724fa0-ca5c-4f4e-a2eb-44c8d1d1f1c2",
  "expense_status": "rejected",
  "reject_reason": "Receipt is required for travel expenses over $20. Please attach the taxi receipt."
}
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:
ValueMeaning
"user"The employee or admin explicitly set this value.
"generated"Auto-generated by Tesouro when a receipt was matched (e.g., ledger_account_id and description).
"automatched"Set automatically by Tesouro — either by the AI receipt matching engine (receipt_id) or from the employee’s profile (location_id, department_id).
nullThis field hasn’t been set yet.
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:
ValueMeaning
"ocr"The receipt was created from a file upload. Tesouro’s OCR service extracted the structured fields.
"user_specified"The receipt was created by sending structured data directly to POST /receipts — no file, no OCR.
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.