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

# Compliance checks

> Check invoices against agreements and keep every completed result.

Check a completed invoice against the agreements it is matched to. Each completed evaluation is an
immutable `ComplianceCheck`, including evaluations that found nothing. Work that fails or is
cancelled never produces a check.

```bash theme={null}
curl -i -X POST "$WATCHDOG_API_URL/v1/invoices/$INVOICE_ID/check" \
  -H "Authorization: Bearer $WATCHDOG_API_KEY" \
  -H "X-Organization-Id: $WATCHDOG_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{"agreement_ids": ["'$AGREEMENT_ID'"]}'
```

Omit the body to check every eligible matched agreement. Supplied agreements are all-or-nothing. One
check takes at most 20 agreements; an invoice with more returns `409 conflict` until you supply
`agreement_ids`. New work returns `202` with `workflow_run_id` and a `Location` header. Starting, retrying
and cancelling require Write access. Reading checks, findings and workflow status requires Read.

## What is eligible

The invoice must be completed, not a credit note, not covered by an active credit-note match, and
have a primary document. The agreement must be matched to the invoice, active, and ready: it has a
supplier, a title, usable content, and every terms document has finished processing. Matching is a
separate command and is not run by a check.

No claim may hold the pair: an invoice and agreement pair is not checked again while a claim that is
not cancelled holds one of its alerts that is not dismissed. A check replaces all of a pair's alerts,
so this keeps claimed alerts, and the rest of that evaluation, as they were claimed.

| Response                     | Meaning                                                                                                         |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `409 no_eligible_agreements` | Nothing is eligible, or a supplied agreement is not. `details` lists rejected IDs. Nothing starts or is billed. |
| `409 check_in_progress`      | A selected pair has unfinished work. `workflow_run_id` is included when it was started through this API.        |
| `503 service_unavailable`    | Dispatch was not confirmed. Inspect the run in `Location` before retrying.                                      |

`Idempotency-Key` is optional. A replay returns `200` with the original run. Without a key, the
in-progress guard still prevents two checks of the same pair from running at once.

## Follow the work

One workflow run groups the request. One item evaluates one invoice and agreement pair. The selected
agreements, the agreement version and the invoice fingerprint are captured at admission.

```bash theme={null}
curl "$WATCHDOG_API_URL/v1/workflow-runs/$RUN_ID/items" \
  -H "Authorization: Bearer $WATCHDOG_API_KEY" -H "X-Organization-Id: $WATCHDOG_ORG_ID"
```

A completed item's `result` contains `compliance_check_id`, `finding_count` and `completeness`. A
failed item carries one of these codes:

| Code                       | What happened                                                                       | What to do            |
| -------------------------- | ----------------------------------------------------------------------------------- | --------------------- |
| `stale_input`              | The invoice or agreement changed after the inputs were read. Nothing was published. | Submit a new check    |
| `superseded`               | Other work replaced this item before it could publish.                              | Inspect the newer run |
| `not_eligible`             | The pair stopped being eligible while the check ran.                                | Resolve, then check   |
| `investigation_incomplete` | The investigation ran out of budget without establishing a result.                  | Retry                 |
| `check_failed`             | The evaluation could not be completed.                                              | Retry                 |
| `execution_incomplete`     | Processing ended without a result.                                                  | Retry                 |
| `dispatch_failed`          | The work could not be started.                                                      | Retry                 |
| `admission_expired`        | The work was not started in time.                                                   | Submit a new check    |

Cancel with `POST /v1/workflow-runs/{id}/cancel`. Cancelled work never replaces current findings.

## Retry

```bash theme={null}
curl -i -X POST "$WATCHDOG_API_URL/v1/invoices/$INVOICE_ID/check/retry" \
  -H "Authorization: Bearer $WATCHDOG_API_KEY" -H "X-Organization-Id: $WATCHDOG_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{"workflow_run_id": "'$RUN_ID'"}'
```

Retry creates a new run linked to the named one and reruns its failed or cancelled items, or the
subset in `item_ids`. Eligibility is verified again and current inputs are captured. Completed items
are never rerun by retry. To reevaluate a completed pair, submit a new check.

## Read results

`GET /v1/compliance-checks` lists completed checks, newest first. Filter with `invoice_id`,
`agreement_id`, `completed_from` and `completed_through`. `GET /v1/compliance-checks/{id}/findings`
returns the findings in their original order. Each finding has its own stable `id`.
`GET /v1/compliance-checks/{id}/transcript` shows how the check reached its result: the display
form of each tool call, in order, followed by its final summary when it fits the transcript size
limit. Prompts and reasoning are not retained. `completed_from` and `completed_through` are calendar dates in your organization's
timezone.

A check records what it read: the agreement version, the invoice fingerprint, and the identity and
checksum of each source document. Each finding carries the invoice line values, the expected values,
the cited excerpts and the referenced price item values as they were at publication. None of this
changes when the invoice is edited, a price is deleted, a document is unlinked, or the resulting
alert is edited or dismissed.

`freshness` is derived when you read a check. `outdated` means the invoice evidence or the agreement
version has changed since. The check itself is unchanged. The newest check for a pair is first in the
list.

## Check an agreement

```bash theme={null}
curl -i -X POST "$WATCHDOG_API_URL/v1/agreements/$AGREEMENT_ID/check" \
  -H "Authorization: Bearer $WATCHDOG_API_KEY" -H "X-Organization-Id: $WATCHDOG_ORG_ID"
```

An agreement check covers the agreement's matched invoices that have no current result: never
checked, or last check failed or was cancelled. Those are the invoices
`GET /v1/invoices?agreement_ids=$AGREEMENT_ID&alert_scope=selected_agreements&alert_checked=false&alert_checkable=true`
lists, apart from invoices already being checked, invoices without a primary document and pairs
a claim holds. Send `{"invoice_ids": [...]}` (up to 20) to check
a sample of them. Completed checks are kept, including outdated ones; check the invoice to
re-evaluate one. The response is `202` with `workflow_run_id`, `invoice_count` and `has_more`: one
request takes at most 5000 invoices, newest first, so repeat it after the run when `has_more` is
true. The agreement must be active and ready (`409 agreement_not_checkable`); with nothing to check
the response is `409 nothing_to_check` and nothing starts.

`POST /v1/agreements/check` does the same for every active, ready agreement and returns
`workflow_run_ids`, one agreement check each, and `has_more`. The 5000-invoice limit applies to the
request in total, filled agreement by agreement in ID order, newest invoices first. Every admitted
run is returned: one whose dispatch failed fails, its items with `dispatch_failed`, and one whose
acceptance was not confirmed either starts or, if it has not started five minutes after admission,
fails with `admission_expired` items. Either way its invoices are unchecked again.
With nothing to check it returns `200` with an empty list.

An invoice check captures its inputs at admission; an agreement check's item reads them when it
starts, so `stale_input` means they changed after that. `/check/retry` applies to invoice checks
only: to retry an agreement check, request it again, since failed and cancelled pairs count as
unchecked.

Items otherwise behave like invoice check items, with the same failure codes. A pair whose agreement is deactivated,
or that a claim comes to hold, while the check waits fails `not_eligible` without replacing its
alerts (a pair a claim holds keeps its last result), so agreements can be edited or deactivated during a check.

## Recheck a topic

`POST /v1/alert-topics/{id}/recheck` checks the topic's agreement again against every invoice
behind the topic's alerts, completed checks included, as one agreement check. Each invoice's check
replaces all of that invoice's alerts for the agreement, including alerts in other topics; dismissed
or credited alerts come back as new pending alerts if the check finds them again. Invoices a claim
holds, invoices already being checked and invoices that cannot be checked are left as they are and
counted in `skipped_count`. The response is `202` with `workflow_run_id`, `invoice_count` and
`skipped_count`; with nothing to recheck it is `409 nothing_to_check`. A pair whose check fails
keeps its alerts and counts as unchecked, so the next agreement check retries it. A recheck is not
idempotent: once its run finishes, the same request starts a new one.

## Reset checks

`POST /v1/invoices/{id}/check/reset` and `POST /v1/agreements/{id}/check/reset` start an invoice's or
an agreement's evaluation over: unfinished checks are cancelled, the pairs' alerts are deleted, and
every pair is marked unchecked so an agreement check picks it up again. A pair a claim holds keeps
its alerts and its result. Completed checks are kept. The response reports `deleted_alert_count`,
`kept_alert_count` and `cancelled_check_count`.

When matching removes an invoice's match to an agreement, that pair is reset the same way, so only
a pair a claim holds keeps its alerts after losing its match.

## How checks relate to alerts

Publishing a check replaces all of the pair's previous alerts with its findings, including dismissed
or credited ones, which come back as new pending alerts if the check finds them again. A pair a
claim holds is never checked, so its alerts stay as they were claimed. A finding's `alert_id` names
the alert it became. A later check may replace that alert, and the finding
keeps the original id. An alert's
`provenance.retained_check` links back to the check that produced it. The invoice `alert_summary`
reports running, failed, cancelled and outdated checks from the same work.
