Recipe: expense capture

Turn the receipts and invoices in a user's mail into expense records they review and approve in your application.

Why it matters: nobody forwards receipts or types amounts. Expenses arrive with their source email and file attached, the uncertain values are flagged for a quick check, and approved records are ready for the books.

Scopes: operations:expenses:write (includes operations:expenses:read), and profile:read to open the original email. Webhooks: expenses.first_completed, expenses.completed.

1. Find the expenses to review

Expenses with every required field filled wait in pending_approval. Search for them, newest first:

curl https://api.bind.co/v1/operations/expenses/search \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"status": ["pending_approval"], "sort_by": ["-expense_date"], "page_number": 1, "page_size": 50}'
{
  "items": [
    {
      "operation_id": "op_5b1f0c7e-8a2d-4c3b-9e61-2f7d4a9c0b13",
      "operation_type": "expenses",
      "status": "pending_approval",
      "vendor_normalised": {"value": "Delta Air Lines", "bind_inference_confidence": 0.97},
      "expense_date": {"value": "2026-10-14", "bind_inference_confidence": 0.99},
      "total_amount": {"value": "412.60", "bind_inference_confidence": 0.98},
      "currency_code": {"value": "USD", "bind_inference_confidence": 0.99},
      "evidence": [
        {"evidence_type": "email", "role": "primary", "created_at": "2026-10-14T16:04:30Z", "file": null,
         "email": {"email_id": "eml_16nJ9iKYhhC", "email_sender": "receipts@delta.com", "email_timestamp": "2026-10-14T16:01:47Z"}}
      ]
    }
  ],
  "total_items": 1, "total_pages": 1, "page_number": 1, "page_size": 50
}

Search "status": ["active"] for expenses not yet ready for review; each one's assessment.completeness_state says whether a required field is still missing (incomplete).

2. Show what needs attention

Use each field's bind_inference_confidence to decide where the user should look. A simple rule: show values at 0.9 or above as they are, and highlight the rest for review. Tune the threshold on your users' data; the score is bind.co's relative confidence, not a probability (see Reading confidence).

Show the evidence next to the record: the email's sender and date, and for a file its name and file_summary. GET /v1/emails/{email_id}/web-link gives a link that opens the original email in the user's mailbox; it needs profile:read.

3. Let the user decide

The user Call Result
Approves it as it is POST /v1/operations/expenses/{operation_id}/confirm approved
Fixes a value PATCH /v1/operations/expenses/{operation_id} with only the fields changed corrected
Says it is not an expense POST /v1/operations/expenses/{operation_id}/reject rejected

A correction:

curl -X PATCH https://api.bind.co/v1/operations/expenses/op_5b1f0c7e-8a2d-4c3b-9e61-2f7d4a9c0b13 \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"total_amount": "412.00"}'

Once a person has decided, later mail never changes the values: new evidence is still listed under the record. To make sure you change the version the user saw, read the record with GET /v1/operations/expenses/{operation_id} (search answers carry no ETag) and send its ETag in If-Match; see Pagination and idempotency.

4. Stay current

On expenses.completed, search again, for example sorted by newest:

{"type": "expenses.completed", "data": {"user_inbox_fetch_id": "fch_4hT0q2Lw9Za", "operation_type": "expenses", "operations_created": 3, "operations_updated": 5}}

Duplicates bind.co finds later are merged into one record; a stored operation_id that starts answering 404 was merged. The Operations guide has the full lifecycle.

Make it yours