Operations

An operation is a business event bind.co found in the user's mail: an expense, a payroll change, a purchase financed with a loan. It is the record your application acts on. This guide covers what an operation is built from, its lifecycle from found to decided, what a person can do with one, how duplicates are handled, how documents feed operations, and how the chaser follows up on operations that are missing something.

Operation types

Each kind of operation is a type, such as expenses. GET /v1/operations lists every type with the scopes it needs, and GET /v1/operations/{operation_type}/schema describes every field; neither needs a token, so you can read them before your application is approved. New types appear in that list without a new API version, and their routes follow the same pattern. Reading a type needs its own scope, such as operations:expenses:read; changing it needs operations:expenses:write, which includes operations:expenses:read. The examples below use expenses.

What an operation looks like

{
  "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},
  "assessment": {"bind_inference_confidence": 0.91, "completeness_state": "complete"},
  "chaser_status": null,
  "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"}}
  ],
  "schema": {"operation_type": "expenses", "version": 3}
}

Reading confidence

bind_inference_confidence is bind.co's relative confidence in a value, between 0 and 1. Use it to rank and to choose what a person should look at; it is not a calibrated probability, so 0.9 does not mean "right nine times in ten". Compare scores within one field, not across fields: 0.9 on an amount and 0.9 on a drafted email body are not the same kind of certainty. Pick your thresholds on your own users' data.

A value a person set reads 1. That says who decided, not that the value is certain: a person can enter a wrong amount. To tell a person's decision from bind.co's reading, use the record's status (approved, corrected and rejected are a person's decisions) and, on profiles, contacts and roles, truth_updated_at (set when a person saved it).

Where operations come from

When more mail or files about the same event arrive, they attach to the same operation.

Lifecycle

Status Meaning Returned by reads
active Found and open; required fields may still be missing Yes
pending_approval Every required field is filled; waiting for a person Yes
approved A person confirmed it as it is Yes
corrected A person confirmed it with changes Yes
rejected A person said it is wrong Yes
merged Folded into another operation No
deleted Hidden by a person No

Until a person decides, bind.co keeps an operation up to date as evidence arrives: an active operation can fill its missing fields and move to pending_approval. Once a person has confirmed, corrected or rejected it, new evidence, including evidence from a merge, is still listed under it, but its values stay as the person left them unless a person resets it.

What a person can do

Action Route From To
Confirm POST /v1/operations/expenses/{operation_id}/confirm active, pending_approval approved
Correct PATCH /v1/operations/expenses/{operation_id} active, pending_approval, approved, corrected corrected
Reject POST /v1/operations/expenses/{operation_id}/reject active, pending_approval rejected
Delete DELETE /v1/operations/expenses/{operation_id} any status reads return deleted
Restore POST /v1/operations/expenses/{operation_id}/restore rejected, deleted the status it had before
Reset POST /v1/operations/expenses/{operation_id}/reset approved, corrected back to bind.co's values, waiting for a decision

PATCH is partial: send only the fields you change, and null clears one. Nothing is ever erased: a deleted operation is kept and can be restored. Reset does not apply to an operation your application created by hand, since there is nothing bind.co read for it to go back to.

A typical review screen: search with "status": ["pending_approval"] for operations ready to decide, show each with its evidence, and offer confirm, correct and reject. Search active to show operations still being completed.

One event, one operation

bind.co checks for duplicates and merges them on its own. A person can merge too: POST /v1/operations/expenses/{operation_id}/merge with the duplicates in loser_operation_ids. They must be active, and this operation must not be rejected. The duplicates become merged and their evidence moves to this operation. What happens to this operation's values depends on its status:

POST /v1/operations/expenses/{operation_id}/unmerge undoes merges, whoever made them: each operation comes back active with its own id and evidence. Once separated, bind.co never merges that pair again on its own. If your application stores operation ids, a stored id that starts answering 404 was usually merged into another one.

Documents

Many operations rest on documents. bind.co recognises the documents in each file, such as an invoice, a receipt or a passport, reads their fields, and uses them to start or complete operations. In an operation's file evidence, doc_types lists each document with its kind, its fields, and requirement_set_satisfied, which says whether it has every field its kind requires. An operation type can also require documents; a missing or incomplete required document is one of the gaps the chaser follows up on.

The chaser

An operation that is still active and incomplete is missing something: a field, a value bind.co is not sure enough about, or a required document. The chaser is bind.co's follow-up on those gaps. Not every operation type is chased, and an operation the chaser has never looked at has a null chaser_status.

It works in three phases, in order.

1. self_help. bind.co looks for the missing value in the user's other operations and the files already in their mailbox, and offers candidates:

{
  "operation_id": "op_5b1f0c7e-8a2d-4c3b-9e61-2f7d4a9c0b13",
  "chaser_status": "in_self_help",
  "gaps": [{
    "canonical_path": "expense_date",
    "reason": "absent",
    "candidates": [{"canonical_path": "expense_date", "source_operation_id": "op_3f6c2a9e-8b1d-4c57-9e0a-5d2f7b8c1e44",
                    "observed": {"value": "2026-10-14", "bind_inference_confidence": 0.94}}],
    "document_candidates": []
  }]
}

Accepting a candidate (.../chaser/self-help/confirm) adds its evidence to the operation and cannot be undone. Declining it (.../chaser/self-help/reject) removes it from the list for good.

2. counterpart_outreach. bind.co prepares a message asking the other party for what is missing:

{
  "operation_id": "op_5b1f0c7e-8a2d-4c3b-9e61-2f7d4a9c0b13",
  "chaser_status": "pending_approval",
  "counterpart_name": "Dana Ruiz",
  "counterpart_email": "dana@acme.com",
  "missing_item": "the invoice date",
  "subject": "Invoice INV-2207: date missing",
  "body": "Hi Dana, could you confirm the date on invoice INV-2207? Thanks, Maya"
}

A person approves it (.../chaser/outreach-approval/confirm) or rejects it (.../chaser/outreach-approval/reject), optionally with one of the operation's allowed reasons. Approving records the decision; it sends nothing. One decision per operation. Seeing these messages needs drafts:read, and deciding needs drafts:write (which includes drafts:read), as well as the operation type's scope.

3. owner_escalation. bind.co hands the operation to the user, with what is still missing. They fill it with the correct and confirm routes above, or stop the chase with .../chaser/owner-escalation/dismiss, which leaves the operation itself unchanged.

Each phase has a queue, for example GET /v1/operations/expenses/chaser/self-help, and GET /v1/operations/expenses/{operation_id}/chaser shows where one operation stands: its phase, its gaps and its next step. Search with chaser_status to find operations in a given state.

Webhooks

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

operations_created counts new operations; operations_updated counts emails and files that attached to existing ones, so one operation can count more than once. Your application receives these events only when it holds the scopes the type needs. On each one, search the type again, for example sorted by newest. The webhooks guide has every payload.