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

# Invoices and financial validity

> Understand invoice values, financial errors, source snapshots, and safe editing.

An Invoice is a published invoice or credit note. Import progress, unusable extraction, duplicates,
and non-invoice files are represented by imports rather than invoice lifecycle states.

## Duplicate invoices

Structured creation and document imports use the same business identity: supplier, recipient,
invoice number, issue date, category, currency, and normalized total including VAT, within your
organization. Invoice numbers are trimmed, totals are compared to two decimals, and credit-note
amounts use their canonical negative sign.

An existing active invoice blocks another structured creation with `409 Conflict`. An import
completes with `outcome: "duplicate"` and the existing invoice ID, without publishing another invoice
or charging another invoice-processing usage event. The duplicate PDF header check also avoids
unnecessary line extraction. The rule applies even when source files differ and no idempotency key
is supplied. Missing identity fields are never guessed; an incomplete import can still rely on the
primary-file checksum guard.

Identity changes on active invoices and restoring deleted invoices use this guard too. A deleted
invoice does not block creation by business identity; importing its exact original file still
returns the existing invoice through the checksum guard.

## Financial validity

`financially_valid` and `financial_errors` are computed, read-only arithmetic results. They do not
establish extraction accuracy, agreement compliance, or human approval. A valid invoice has an empty
financial error list; detailed line errors appear with the paginated line items.

```json theme={null}
{
  "financially_valid": false,
  "financial_errors": [
    {
      "code": "invoice_total_mismatch",
      "field": "total_amount_including_vat",
      "message": "The invoice total does not match the sum of its lines including VAT."
    }
  ]
}
```

Checks compare each line's quantity, price, discount, and surcharge with its line total, then compare
summed lines with the invoice's stated totals, including VAT. Rates are fractions: `"0.25"` means
25%. The rounding tolerance is inclusive at **1 invoice currency unit**; a difference greater than
1 fails. Currency, a total, and at least one line are necessary, and the total's sign must agree
with the invoice category.

| Operation           | Financial rule                                                       |
| ------------------- | -------------------------------------------------------------------- |
| Structured creation | Reject inconsistent data and commit nothing                          |
| Import publication  | Keep structurally usable invoices even when financially inconsistent |
| Financial editing   | Require the complete resulting invoice to pass                       |
| Metadata editing    | Allow existing financial inconsistencies to remain                   |

A successful import can therefore produce an invoice with `financially_valid: false`. Correct the
financial values when appropriate, or retain the source's values and errors for investigation.

## Values, parties, and source information

Amounts remain decimal strings. Unknown amounts and dates remain `null`; missing payment evidence
produces `payment.status: "unknown"`. Financial validity does not imply payment.

Supplier and recipient objects contain invoice snapshots alongside canonical party IDs. These
captured names, organization numbers, and addresses describe the invoice; they are not current
party master records. Changing a canonical party ID changes the relationship while preserving the
captured snapshot.

`origin` describes structured creation, documents, integrations, or an unknown legacy origin.
Original financial values and source documents remain available after editing. Extraction
confidence is projected as `extraction_confidence`: high, mid, low, or unknown. Both successful
header and line assessments from the creating import are required; the lower level wins.
Structured/XML creation and unavailable history remain unknown. Manual confirmation and arithmetic
do not change extraction confidence. Legacy mutable confidence flags are not used.

## Editing an invoice

PATCH merges supplied fields with the current invoice. If you send `line_items`, it is the
**complete resulting line collection**: retain existing IDs, omit lines to remove them, and omit
the ID on a new line. Line IDs must belong to that invoice. Supplied `delivery` replaces the whole
delivery object; `null` clears it.

Changing amounts, quantities, prices, rates, currency/conversion, category, or line membership
validates the entire resulting invoice. A failure rolls back every supplied change. Equivalent
numeric representations and unchanged values are no-ops. Metadata edits preserve existing
financial errors.

Saving does not approve an invoice or refresh compliance analysis. Existing findings remain
results of their previous analysis. Relevant invoice changes make those assessments outdated
until a new check runs. Delete soft-deletes an invoice; restore makes it active again.
Both operations are repeatable. A restore can conflict with another active invoice's business
identity, in which case the original stays deleted.

Use the [quickstart](/api-preview/quickstart) for requests and the generated endpoint pages for
complete create/update schemas, invoice details, line items, documents, filters, and metrics.

## Managing invoice documents

Upload bytes using `POST /v1/documents/upload` and its signed PUT URL, then attach the returned
Document ID. All three commands require **Write** access and work on published invoices, including
soft-deleted invoices. Attachments must be **100 bytes to 25 MiB each**. There is no attachment-count
or combined-size cap, for these commands or new REST imports. Import primaries retain the 50 MiB limit.

```bash theme={null}
curl --fail-with-body "$API_URL/v1/invoices/$INVOICE_ID/documents" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H 'Content-Type: application/json' -d "{\"document_id\":\"$DOCUMENT_ID\"}"
curl --fail-with-body -X POST "$API_URL/v1/invoices/$INVOICE_ID/documents/$DOCUMENT_ID/set-primary" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID"
curl --fail-with-body -X DELETE "$API_URL/v1/invoices/$INVOICE_ID/documents/$OLD_DOCUMENT_ID" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID"
```

Attach returns `201`, or `200` with the current relationship when already attached. New attachments
are non-primary even on invoices without documents. Selecting a primary returns `200` and keeps
positions unchanged. Unlink returns `204`, including repeated requests after authorization.
Promote another document before unlinking the primary. An unreleased claim membership blocks
removal, including a cancelled claim; remove the alerts from that claim first.

Unlinking retains the Document and downloadable bytes, original import sources, and prior findings.
Changes update the invoice evidence fingerprint without changing financial values or running
extraction, matching, or checks. Repeating an unchanged command preserves evidence and timestamps.
Missing or changed uploaded bytes return `409 source_unavailable`; invalid sizes return `400`.
Relationship conflicts return `409 conflict`; missing or foreign resources return `404`.

## Importing PDF and XML documents

PDF imports extract the primary invoice's header, then its lines. Attachments provide supporting
context; a different invoice in an attachment does not replace the primary document.

XML imports support **PEPPOL BIS Billing 3.0 UBL Invoice and CreditNote** documents. These are
parsed deterministically, without model calls or generated descriptions. The primary XML supplies
the business values; attachments are inspected and retained without overriding those values.
Other primary XML formats fail with an actionable unsupported-format message. There is no model
fallback for unsupported or structurally unusable XML.

The parser preserves stated totals, decimal precision, identifiers, and source-language text.
Missing required values fail extraction; unknown optional values stay null. Explicit document
allowances and charges become signed adjustment lines. Constructs that cannot be represented
faithfully by the invoice contract, such as line adjustments outside the supported rate range,
fail with an explanation. XML parsing does not establish full Peppol conformance.

Structurally usable XML with inconsistent financial values still produces an invoice with
`financially_valid: false`, using the same financial checks as PDF imports. Invoice totals represent
the total including VAT, which may differ from an outstanding amount after earlier payments.

Import detail includes bounded evidence for `inspection`, `header`, `lines`, or `xml` stages.
Each summary describes the latest saved result for that stage. A new result replaces the previous
one; this is not a history of extraction attempts. Workflow runs retain their execution outcomes.
XML evidence has null extraction confidence. Retry may reuse compatible successful stages;
source versions must still be available and unchanged. Evidence and model usage remain separate
from the published Invoice.

## Alert coverage

Invoice list and detail include `alert_summary`, with check coverage and retained alert counts.
Use `alert_checked`, `alert_checkable`, and `has_open_alerts` to filter invoices. These observations
are separate from financial validity and approval. See [Alerts and check coverage](/api-preview/alerts).

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

## Selection, ordering, and exports

Lists, metrics, and `GET /v1/invoices/export` share the same selection. Use `join_operator=and|or`
(default AND) for a flat group of column filters; each inclusive date range remains one AND
condition. Organization, deletion, search, `agreement_ids`, and mine/team scope always stay outside
that group. Query selections accept at most 50 comma-separated entries, normalized and deduplicated.
Omission adds no filter; empty or invalid entries return 400.

Supplier, recipient, category, currency, confidence, and check-status selections have corresponding
`*_operator=is|is_not` controls (`supplier_operator`, `recipient_operator`, `category_operator`,
`currency_operator`, `confidence_operator`, `check_status_operator`). Nullable party/currency values
use literal `null`. Excluding NOK includes unknown currency unless you also exclude `null`.

Sort by `supplier` or `recipient` to use current canonical names across the full selection. Responses
include `supplier_name` and `recipient_name`; snapshots remain separate. Nulls sort last and invoice
IDs break ties. Default ordering remains `created_at desc`; use `sort=issued_date&direction=desc`
for an invoice-date list. Search still matches invoice number/title.

Use `financially_valid`, `confidence_levels=high,mid,low,unknown`, `has_recorded_match`,
`has_open_alerts`, and `check_statuses` independently. Status values are `not_checkable`, `checking`,
`not_checked`, `completed`, and `incomplete`. Recorded relationship counts describe an invoice's
linked credit notes or a credit note's matched invoices; a match does not prove financial resolution.
`alert_summary.is_fully_credited` is true only when one completed, nondeleted credit note uniquely
targets the invoice and offsets its total including VAT within ±1 currency unit in the same
currency. Both documents must be completed and nondeleted with nonzero canonical amounts.
Partial notes are not summed, and shared notes do not qualify. Any linked completed, nondeleted credit note blocks checks, including partial and shared matches.
Retained links to deleted, unfinished, or reclassified documents do not block checks.
`has_credit_note_match` continues to report the relationship independently.

`alert_scope=selected_agreements` requires `agreement_ids` and restricts coverage/counts to currently
matched selected agreements. Default `invoice` scope evaluates overall coverage. Responses identify
scope explicitly in `alert_summary.scope.type` (metrics: `alert_scope.type`), with value `invoice`
or `selected_agreements`. Each scope object also includes `agreement_ids`: null for overall coverage
or the normalized requested selection for selected scope.
A check completed for A can give completed coverage in A's view while missing B makes overall coverage
incomplete. No findings is distinct from completed current coverage.

Detail also exposes stored payment terms, invoice-wide notes and additional properties. Paginated
lines preserve separate line notes, additional properties and allowance/charge evidence. Null means
unavailable; empty collections stay empty. `GET /v1/invoices/imports?limit=1` includes `has_ingestion` alongside `data` and `next_cursor`
for retained Imports, invoice rows or invoice-category Documents, including legacy ingestion. Use
this organization-wide signal when an empty invoice list needs an onboarding decision. It is
independent of Import status filters and pagination; normal invoice lists do not run this query.

CSV export accepts `granularity=invoice|invoice-items` (default invoice), all selection filters, and
no pagination/sort parameters. It exports every selected invoice or stored line in UUID order using
bounded live batches. Values use exact decimal strings, blank nulls, JSON arrays, formula-safe text,
UTF-8 BOM and CRLF. Empty results contain column headers. Changes during traversal can affect later
rows; it is not a snapshot. HTTP 200 alone does not establish completion: interrupted streams fail,
and retry starts a fresh download. No resumable download exists. Generated clients should request
`parseAs: 'stream'` with an `AbortSignal` and forward the body to a writable destination without Blob
buffering.
