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

# Alerts and check coverage

> Find, edit, dismiss, and reopen alerts, and read invoice check coverage.

Alerts are stored findings. Reading or editing an alert does not start a new check.

## Find the alerts you need

```http theme={null}
GET /v1/alerts?invoice_ids=INVOICE_ID&statuses=pending,claimed
GET /v1/alerts?agreement_ids=AGREEMENT_ID&topic_ids=TOPIC_ID,null&sort=impact_amount
GET /v1/alerts/metrics?agreement_ids=AGREEMENT_ID
GET /v1/alerts/groups?group_by=topic&agreement_ids=AGREEMENT_ID
```

All retained alerts are included by default. The list, metrics, groups, export, and bulk commands
share one set of filters, so what you count is what you act on:

* Resources: `invoice_ids`, `agreement_ids`, `supplier_ids`, `recipient_ids`, `tag_ids`,
  `topic_ids`, `claim_ids`, each with an `excluded_…_ids` counterpart where exclusion is useful.
  `topic_ids=null` selects uncategorized alerts and `claim_ids=null` alerts in no claim; combine
  them, as in `topic_ids=TOPIC_ID,null`.
* Values: `statuses`, `claim_statuses`, `confidence_levels` (`high`, `mid`, `low`, `null`),
  `correction_types`, `currency_codes`, `freshness`.
* Ranges: `impact_min`/`impact_max` on the organization-currency impact, `issued_from`/`issued_through`
  for the invoice date, and `created_from`/`created_through` as calendar dates in your organization's
  timezone.
* `search` matches alert, invoice, line, supplier, agreement, and topic text.

Sort alerts by `created_at` (default), `impact_amount` (organization currency), `issued_date`,
`invoice_number`, `supplier`, or `status`. Each alert carries its confidence, the impact in its own
currency and in your organization's currency, the invoice with its dates and total, the invoice
line, the agreement and its current version, the topic and whether it is locked, the current
claim, and links to the alerts it replaces or is replaced by.

Groups return the same totals per agreement, invoice, topic, claim, status, or freshness, sorted by
impact, count, or title. Each group's `key` is the value to put in that dimension's filter, so a
group expands into its alerts or into subgroups with one more request. `GET /v1/alert-topics`
includes empty topics.

`GET /v1/alerts/export?format=csv` or `format=xlsx` downloads the matching alerts; XLSX adds a
Topics sheet. `GET /v1/compliance-checks/{id}/transcript` shows the tool calls and final summary of
the check behind an alert (`provenance.retained_check`).

Write null as the string `null`, in query strings and in JSON selection bodies alike.

New alerts join topics when the agreement's `alert_topic_reconciliation` workflow run completes.
A compliance check requests one for each agreement whose check completed; if that request fails,
the check is unaffected and the next one requests again. `POST /v1/agreements/{id}/alert-topics/reconcile`
requests one explicitly and returns its `workflow_run_id`: 202 when a run starts, 200 when one is
already queued or running.

Responses use `data` and `next_cursor`. Keep the same filters and sort when requesting the next
page. Amounts are decimal strings. Individual unknown impacts remain null. Aggregate totals treat null impacts as zero and include a missing amount count.

Invoice fingerprints start tracking on the first evidence edit. A null current invoice fingerprint
does not invalidate an existing check; missing assessment provenance remains unknown.

## Edit an alert

Fetch `GET /v1/alerts/{id}`, then send the returned `ETag` in `If-Match`:

```http theme={null}
PATCH /v1/alerts/ALERT_ID
Content-Type: application/json
If-Match: "ETAG_FROM_GET"

{"title":"Incorrect price","expected":{"base_price":"99.50","quantity":"2"}}
```

Send only changed fields. Omitted fields stay unchanged. Prices and quantities use decimal strings;
rates use fractions. Derived amounts are recalculated. A `412` response means the alert changed:
fetch it again and review the changes before retrying. Narrative and citation changes save together.

GET and PATCH use the same `expected` object for proposed line details, pricing inputs, and
explanations. Supplying one nested field preserves the others. `expected.net_price` and
`expected.total` are calculated and read-only.

GET also includes read-only `invoiced` pricing captured at alert creation and an `invoice_item`
with the linked line's ID, product code, description, and unit. These line details reflect the
current invoice; `invoice_item` is null when no accessible line is linked.

Use `POST /v1/alerts/{id}/dismiss` with `{}` or a category and note to dismiss an alert while keeping its claim attachment.
Use `POST /v1/alerts/{id}/credit` without a body to record that the supplier has credited the finding.
Use `POST /v1/alerts/{id}/reopen` without a body to set an alert back to pending. A member of an active claim stays attached; a member of a cancelled claim is released.
Repeated commands are no-ops. Alerts in completed claims cannot be changed.

A dismissal with a category or note may teach Watchdog something about the agreement. It starts an
`agreement_context_suggestions` workflow run that can propose edits to the agreement's
instructions; review them with
[agreement context suggestions](/api-preview/agreements#review-suggested-instruction-edits). The
dismissal does not wait for the run.

## Act on many alerts

`POST /v1/alerts/dismiss`, `/credit`, and `/reopen` apply the same commands to a selection. Send
either `alert_ids` or a `filter` that uses the alert list filters as JSON lists and booleans, with
optional `excluded_alert_ids`:

```json theme={null}
{ "filter": { "topic_ids": ["TOPIC_ID"], "statuses": ["pending"] }, "category": "wrong_alert" }
```

Each command is one transaction, changes at most 5000 alerts, and returns `changed_count` and
`skipped_count`. With `alert_ids`, an unknown alert returns `404` and an alert in a completed claim
returns `409`. With a `filter`, such alerts are left alone and counted in `skipped_count`. A filter
also never dismisses or reopens a credited alert; select it with `alert_ids` instead. The filter
must name `agreement_ids`, `invoice_ids`, or `topic_ids` or `claim_ids` without `null`. Count a
selection with the metrics endpoint first: a dismissal of pending and claimed alerts leaves members
of completed claims unchanged, and `statuses=pending,claimed&claim_statuses=completed` counts those. Alerts already in the requested state are left alone, so a retry is safe. The
claim membership commands and `POST /v1/alert-topics/{id}/alerts` take the same selection.

## Edit topics

`PATCH /v1/alert-topics/{id}` edits `title`, `description` (Markdown), and `key_questions`.
`POST /v1/alert-topics/{id}/lock` protects a topic from automatic categorization and edits;
`/unlock` reverses it. `POST /v1/alert-topics/{id}/alerts` moves the selected alerts into the topic.
`POST /v1/alert-topics/{id}/recheck` checks the topic's invoices again; see
[recheck a topic](/api-preview/compliance-checks#recheck-a-topic).
`GET /v1/alert-topics/{id}` includes the topic's sources under `evidence`, by the numbers its
inline markers such as `[1]` use.

To merge or split topics, request a proposal, review it, then apply it:

```bash theme={null}
curl -i -X POST "$WATCHDOG_API_URL/v1/alert-topic-proposals" \
  -H "Authorization: Bearer $WATCHDOG_API_KEY" -H "X-Organization-Id: $WATCHDOG_ORG_ID" \
  -H "Content-Type: application/json" \
  -d '{"operation":"merge","topic_ids":["'$TOPIC_A'","'$TOPIC_B'"]}'
```

The proposal is prepared by a workflow run; its completed item result holds `proposal_id`.
`GET /v1/alert-topic-proposals/{id}` shows the proposed topics and which alert groups go where.
`POST /v1/alert-topic-proposals/{id}/apply` with `{}` applies it as proposed; you can also edit the
proposed titles, descriptions and questions and, for a split, move groups between the new topics.
Applying creates the new topics and deletes the selected ones in one step. It works once, and only
if the agreement's topics have not changed since the proposal was prepared; otherwise it returns
`409` and you request a new proposal. Locked topics cannot be merged or split.

A proposal run that cannot produce a proposal fails its item with a code such as `topic_locked`
(a selected topic, or one holding alerts replaced together with the selection, is locked),
`not_splittable` (the selection has fewer than two alert groups), `stale_input` (the selected
topics changed or were removed first) or `proposal_failed`, besides the workflow codes
`dispatch_failed`, `admission_expired` and `execution_incomplete`.

## Change supporting citations

Use explicit `upsert` and `remove` operations in the alert PATCH. Existing citations accept partial
field changes. Those changes update the shared citation in every linked alert and topic.
A new reference needs a `citation` selector:

```json theme={null}
{
  "explanation": "The price follows this agreement [2].",
  "citations": {
    "upsert": [
      {
        "source_ref": 2,
        "citation": {
          "source": { "source_type": "document", "document_id": "DOCUMENT_UUID" },
          "quote": "The agreed unit price is 99.50."
        }
      }
    ],
    "remove": [1]
  }
}
```

Citations also support web URLs, agreement item IDs, and supplied context. To reuse an existing
citation without editing it, supply `"citation": { "id": "CITATION_UUID" }`, using `citation_id`
from a read. A selector cannot be combined with outer field edits. Supplying a new citation
changes only this alert's link; changing fields without a selector edits the shared row everywhere.

Citation and document IDs must belong to your organization, and items must belong to the alert's
agreement. Shared edits fail if any linked use is protected by a claim or locked topic.
Verification is server-owned. Document edits are re-verified against stored text; unmatched or
unavailable evidence remains visible and unverified. Saving does not fetch external material,
extract documents, invoke models, or run compliance checks. Assessment freshness stays unchanged.

Omitted citations and fields stay unchanged. When removing references, update all affected inline
markers in the same request. Duplicate or missing references fail without saving any changes.

`GET /v1/alerts/{id}` returns every citation under `evidence`. Excerpts are bounded to 10,000
characters, and `truncated` indicates a shortened excerpt. Send only the fields you deliberately
changed; never replace citations from a shortened read. The response ETag can be used for PATCH.

## Read invoice coverage

Every invoice list item and invoice detail includes `alert_summary`:

```json theme={null}
{
  "checks": { "status": "incomplete", "completed": 1, "total": 2, "reasons": ["outdated"] },
  "counts": { "pending": 2, "claimed": 0, "dismissed": 1, "credited": 0 },
  "has_credit_note_match": false,
  "is_fully_credited": false
}
```

The invoice is fully checked only when every matched, nondeleted agreement has a completed check
against current inputs. Status distinguishes `not_checkable`, `checking`, `not_checked`,
`completed`, and `incomplete`. Reasons preserve missing, failed, cancelled, outdated, and unknown
checks; `invoice_changed` accompanies `outdated` when the invoice itself changed after a completed check. Zero alerts alone does not establish that checking completed.

Use `alert_checked`, `alert_checkable`, and `has_open_alerts` to filter invoices. Invoice metrics
provide matching checked/checkable counts and amounts by currency. Dismissed alerts remain in
counts. `has_credit_note_match` records a relationship independently of full credit.
`is_fully_credited` requires one completed, nondeleted credit note uniquely linked to the invoice,
with a total including VAT within ±1 currency unit of the invoice total in the same currency.
Partial notes are not summed, and a note linked to another invoice does not qualify.
Any linked completed, nondeleted credit note makes an invoice uncheckable with reason `credit_note_matched`. Only qualifying full credit automatically marks findings Credited and enables credited success messaging.
Retained links to deleted, unfinished, or reclassified documents do not block checks.

`financially_valid` remains a separate arithmetic result. The generated endpoint references describe
all fields, filters, permissions, and error responses.

## My records and teams

Use `mine=true` or explicit `team_ids` to filter by shared team rules. See
[My records and teams](/api-preview/conventions#my-records-and-teams) for matching rules,
empty results, errors, and pagination.
