> ## Documentation Index
> Fetch the complete documentation index at: https://endclose.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Exceptions review

> Write a note and a suggested action for each overdue line your own matching engine leaves open

When records in a reconciliation stay unmatched past their `overdue_at`, End Close opens an **exception** for the data stream and your reviewers see the open lines on the Exceptions page, one note per line. End Close writes those notes itself. If you already run your own matching engine, you can write them instead: your engine knows your billing rules, so its reasons are the ones your reviewers want to read.

Lines your rules (or your engine, through `POST /reconciliation_matches`) have already matched are done and need no finding. Write findings for the lines that are left.

## Prerequisites

* An [API key](/docs/authentication) for the environment.
* The reconciliation's id and the id of the data stream whose lines you are reviewing (`GET /reconciliations`).

## Write findings

Send one finding per open record. Identify the record by `record_id`, or by the `external_id` you gave it when you created it. A finding with `suggestion.kind` of `match` or `adjust` becomes the **Approve** action in the review; `looks_right: true` turns the line gray so it can be confirmed in bulk.

```bash cURL theme={null}
curl -X PUT https://api.endclose.com/v1/reconciliations/$RECONCILIATION_ID/data_streams/$DATA_STREAM_ID/findings \
  -H "X-API-Key: $END_CLOSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "findings": [
      {
        "external_id": "1DP200406",
        "note": "Northwind Credit Union was billed for Falcon monitoring on invoice line NW-2026-10-14, as a back-bill for June and July.",
        "category": "back-billing",
        "suggestion": {
          "kind": "match",
          "record_ids": ["NW-2026-10-14"],
          "text": "Match with invoice line NW-2026-10-14."
        },
        "evidence": [
          { "quote": "Falcon monitoring Jun–Jul 2,746.66", "source": "Draft invoice, October" }
        ]
      },
      {
        "external_id": "1DP200494",
        "looks_right": true,
        "note": "A $0.00 line for Harbor State Bank. It was $0.00 last month too and nothing is owed.",
        "category": "zero line",
        "precedent": "September: same client, line 200494, left at $0.00.",
        "suggestion": { "kind": "none" }
      }
    ]
  }'
```

```json Response theme={null}
{
  "exception_id": "01a1138b-9d2e-7f4a-8c01-5e8b2a9c7d10",
  "upserted": 2,
  "errors": []
}
```

This endpoint opens the exception for the data stream if the sweeper has not yet, and reuses the open one otherwise. When you already have the exception id, `PUT /reconciliation_exceptions/{id}/findings` takes the same body and returns `{ "upserted", "errors" }`.

Each finding is checked on its own. A bad record id, an empty note or an unknown record in `suggestion.record_ids` rejects that finding only; the rest are written and the response lists the rejected ones by position:

```json Partial success theme={null}
{
  "upserted": 1,
  "errors": [
    { "index": 1, "external_id": "1DP200494", "message": "external_id is not a record in this exception's data stream" }
  ]
}
```

Findings are keyed on the record, so re-sending a record replaces its finding. Send up to 1,000 per request.

An `evidence` quote can carry a `url` to the document it came from — a contract uploaded to **Files**, say, using the file's `url`. End it with `#page=N` and the review opens the PDF at that page: the row shows a document icon, and the drawer labels the quote's link “Open p.N” and shows the document inline.

<Note>
  `suggestion.record_ids` lists every other record of the proposed match, on either side of the reconciliation — a roll-up names its sibling lines along with the invoice line. End Close recomputes the difference from the records' amounts; a `match` that does not balance is refused when the reviewer approves it, so use `adjust` with a `reason` when a gap is expected.
</Note>

## Read the review back

```bash cURL theme={null}
curl "https://api.endclose.com/v1/reconciliation_exceptions/$EXCEPTION_ID/review?status=all" \
  -H "X-API-Key: $END_CLOSE_API_KEY"
```

Each row carries the record, its finding, a `status` derived on read — `done` once the record is matched, `looks_right` when the finding says so, `to_look_at` otherwise — and `difference_cents`, the gap between the line and what the finding found on the other side. Lines that carry a count (`metadata.quantity`, `qty`, `count`, `event_qty` or `units` on the record) also get `quantity`, `other_quantity` (the suggested records' counts, summed into `finding.other_side.count`) and `quantity_difference`, so a billing review can see 27,468 vs 27,467 without reading the note. `counts` gives the totals per status, `categories` the finding categories with how many lines carry each, and `pending_notes` is the number of overdue lines that have no finding yet. `status` defaults to `to_look_at`; `category` (repeatable) and `q` narrow the page, and `sort` + `dir` order it by any column.

`GET /reconciliation_exceptions/{id}` adds `reasons`, the adjustment reasons already used on the reconciliation.

The full request and response shapes are under **Reconciliation Exceptions** in the API reference.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.