> ## 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.

# Receipts & OCR

> Upload a receipt, let OCR extract the fields, and let auto-matching link it to a transaction — or do it manually.

## Overview

A receipt is a digital file — PDF, PNG, JPEG, or GIF — representing proof of a purchase. Receipts can be created from structured data, uploaded from a file, or forwarded by email. Regardless of how a file arrives, Tesouro runs OCR to extract structured fields, and the AI auto-matching engine then tries to link the receipt to an existing transaction. Receipts created from structured data skip OCR entirely.

## Upload paths

### Direct upload (API or web)

Call `POST /receipts/upload-from-file` with the file as `multipart/form-data`. Tesouro accepts PDF, PNG, JPEG, and GIF files up to 20 MB:

```sh lines theme={null}
curl -X POST 'https://api.sandbox.tesouro.com/v1/receipts/upload-from-file' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -F 'file=@receipt.pdf;type=application/pdf'
```

The response is immediate. The receipt is created with `source_of_data: "ocr"` and OCR processing begins asynchronously:

```json expandable lines theme={null}
{
  "id": "e5ea872b-a3d6-452b-b1d3-7b4e30b8ce2b",
  "created_at": "2026-05-22T12:59:50.507691+00:00",
  "updated_at": "2026-05-22T12:59:50.507704+00:00",
  "created_by_entity_user_id": null,
  "currency": null,
  "description": null,
  "document_id": null,
  "file_id": "e17e0a47-79a5-4672-af79-9a5ce47597f0",
  "file_name": "receipt.pdf",
  "file_url": "https://tesouro-monite-file-saver-stage.s3.us-east-1.amazonaws.com/staging/payables/e17e0a47-79a5-4672-af79-9a5ce47597f0/receipt.pdf",
  "issued_at": null,
  "merchant_location": null,
  "merchant_name": null,
  "ocr_request_id": null,
  "ocr_status": null,
  "origin": "upload",
  "source_of_data": "ocr",
  "total_amount": null,
  "transaction_id": null
}
```

The `ocr_status` field on the receipt tracks where processing is:

| Status       | Meaning                                                                        |
| ------------ | ------------------------------------------------------------------------------ |
| `null`       | OCR hasn't started yet — the receipt was just created.                         |
| `processing` | OCR is running.                                                                |
| `success`    | OCR completed and fields are populated.                                        |
| `error`      | OCR failed. The receipt still exists but structured fields won't be populated. |
| `canceled`   | OCR was canceled before completing.                                            |

When OCR finishes, fields like `merchant_name`, `total_amount`, `issued_at`, and `currency` are populated and you receive a `receipt.ocr_finished` webhook:

```json lines theme={null}
{
  "action": "receipt.ocr_finished",
  "object": {"id": "e5ea872b-a3d6-452b-b1d3-7b4e30b8ce2b"},
  "object_type": "receipt"
}
```

### Email forwarding

Each organization has a dedicated mailbox. Employees forward receipt emails as attachments (PDF, PNG, JPEG, or GIF, up to 20 MB) to that address. Tesouro creates a receipt for each attachment and runs OCR automatically.

To get an organization's mailbox address, call `GET /mailboxes`:

```sh lines theme={null}
curl -X GET 'https://api.sandbox.tesouro.com/v1/mailboxes' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23'
```

```json lines theme={null}
[
  {
    "id": "e8c884f8-bed3-4d92-afc9-3538c5079014",
    "status": "active",
    "related_object_type": "receipt",
    "mailbox_name": "1102737648192968373_receipts",
    "mailbox_full_address": "1102737648192968373_receipts@mg.sandbox.tesouro.com"
  }
]
```

Email-forwarded receipts show `origin: "email"` and the `sender` field contains the forwarding address. The receipt's `source_of_data` is `"ocr"`—OCR still processes the attachment regardless of how it was delivered.

<Note>
  Mailboxes must be created with `related_object_type: "receipt"` to receive expense receipts. Mailboxes with type `"payable"` route to accounts payable, not expense management. See [Mailboxes](/finops/guides/advanced/mailboxes) to set up a custom email domain.
</Note>

### Create a receipt from structured data

If you already have the receipt data in structured form (from a partner system or POS integration), use `POST /receipts` to create a receipt without a file. This skips OCR entirely:

```sh expandable lines theme={null}
curl -X POST 'https://api.sandbox.tesouro.com/v1/receipts' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23' \
  -H 'Content-Type: application/json' \
  -d '{
    "merchant_name": "The Smith Restaurant",
    "merchant_location": "28 West 21st St, New York, NY 10010",
    "description": "Team lunch with new client",
    "currency": "USD",
    "total_amount": 8500,
    "issued_at": "2026-05-22T12:00:00Z"
  }'
```

```json lines theme={null}
{
  "id": "519143ef-22c0-481c-80d2-4348a135bc58",
  "origin": "upload",
  "source_of_data": "user_specified",
  "ocr_status": null,
  "transaction_id": null
}
```

You can also attach a file to an existing receipt at any time using `POST /receipts/{id}/attach-file`.

See [`POST /receipts`](/finops/reference/openapi/receipts/post-receipts) and [`POST /receipts/upload-from-file`](/finops/reference/openapi/receipts/post-receipts-upload-from-file) for the full API reference.

## Auto-matching to transactions

After OCR completes, Tesouro's AI auto-matching engine tries to link the receipt to an unmatched transaction in the same organization. It scores candidates based on amount, currency, merchant name, and timestamp proximity. The receipt is linked to the highest-scoring transaction that clears the match threshold and hasn't already been matched.

If no candidate clears the threshold, the receipt remains unmatched in the Receipts inbox.

When the AI links a receipt, the transaction's `source_of_data.receipt_id` is set to `"automatched"`. At that point, the auto-categorization step fires and populates `ledger_account_id` and `description` on the transaction.

<Note>
  Each receipt can only be linked to one transaction. If a receipt is already matched, the auto-matching engine won't re-link it.
</Note>

## Receipt bank and manual matching

Unmatched receipts accumulate in the Receipts inbox — retrievable via `GET /receipts?has_transaction=false`. Use the `has_receipt=false` filter on `GET /transactions` to find the other side: transactions still waiting for a receipt.

### List unmatched receipts

```sh lines theme={null}
curl -X GET 'https://api.sandbox.tesouro.com/v1/receipts?has_transaction=false' \
  -H 'Authorization: Bearer ACCESS_TOKEN' \
  -H 'X-Organization-Id: ORGANIZATION_ID' \
  -H 'X-Finops-Version: 2025-06-23'
```

### Manual match

When auto-matching doesn't find a match, or links the wrong transaction, manually link a receipt to a transaction by sending `PATCH /receipts/{receipt_id}` with the `transaction_id`:

```sh lines theme={null}
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"}'
```

A manual match sets `source_of_data.receipt_id` to `"user"` on the transaction, and `link_source: "user"` on the receipt. This distinguishes it from an AI-generated match in audit logs and in the UI.

### Unlink a receipt

To remove a receipt from a transaction, send `PATCH /receipts/{receipt_id}` with `"transaction_id": null`. The receipt returns to the Receipts inbox.

See [`GET /receipts`](/finops/reference/openapi/receipts/get-receipts) and [`PATCH /receipts/{id}`](/finops/reference/openapi/receipts/patch-receipts-id) for the full API reference.
