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

# Workflow runs and items

> Follow operation progress, retrieve item results, cancel work, and retry imports or matching.

A **WorkflowRun** tracks one submitted business operation. A **WorkflowRunItem** tracks one
admitted unit of work and retains its result or failure. A single invoice import creates one run
with one item. An accounting sync discovers and adds many invoice items to one run.
Discovery may temporarily have no items, finish with no relevant work, or fail before admitting any.

Create work through domain endpoints such as `POST /v1/invoices/imports`. There is no generic task
submission endpoint. Import admission/retry returns `import_id`, `workflow_run_id`, and
`workflow_run_item_id`; the import's summary/detail also identify its current run and item.
Known primary-file duplicates detected before admission create neither a run nor an item.

The preview exposes `invoice_import`, `invoice_import_batch`, `invoice_agreement_matching`,
`agreement_invoice_matching`, `invoice_compliance_check`, `agreement_compliance_check`,
`alert_topic_reconciliation`, `alert_topic_proposal`, `agreement_context_suggestions`,
`document_extraction`, `agreement_document_classification`, `agreement_detail_suggestions` and
`integration_sync` workflows. An invoice check has one item per agreement and an agreement check
one item per invoice; see
[compliance checks](/api-preview/compliance-checks). After a check, an `alert_topic_reconciliation`
run with one item organises the agreement's alerts into topics; at most one is active per
agreement. Each extraction, classification or detail-suggestion request has one item.
Classification and detail-suggestion items identify the agreement and contain the proposed role
or details; extraction items identify the Document.
See [agreement suggestions](/api-preview/agreements#suggest-a-document-role-or-agreement-details).
Matching items always identify invoices; an invoice refresh
has one item, while an agreement refresh discovers invoice items in bounded pages. See
[Explicit matching](/api-preview/matching) for commands and relationship results. Workflow responses contain business progress and results; source
evidence is available through the import resource.

## Endpoints and permissions

| Endpoint                                     | Purpose                                                     |
| -------------------------------------------- | ----------------------------------------------------------- |
| `GET /v1/workflow-runs/summary`              | Bounded background progress grouped by public workflow type |
| `GET /v1/workflow-runs`                      | Filtered, paginated operation history                       |
| `GET /v1/workflow-runs/{id}`                 | One operation with compact progress                         |
| `GET /v1/workflow-runs/{id}/items`           | Paginated work items, including available results           |
| `GET /v1/workflow-runs/{id}/items/{item_id}` | One item using the same representation                      |
| `POST /v1/workflow-runs/{id}/cancel`         | Cancel remaining work in this exact operation               |

Send a bearer credential and `X-Organization-Id`. Workflow reads require Read; cancellation requires
Write. OpenAPI records each operation's level in `x-watchdog-permission`. Lists include only the
shipped public workflow inventory in the target organization. Unknown, foreign, or internal run IDs
return 404; an item must belong to the requested run and organization. Insufficient access returns 403.

## Background progress

Use `GET /v1/workflow-runs/summary` for background observation. It returns one entry per public
workflow type, including idle types. `run_counts` covers all retained runs by status;
`progress` covers items belonging to currently active runs only. `discovering_run_count` reports
open discovery, in which case `progress.total` is null. A zero total is a valid empty operation.

Each entry has an opaque `change_token` derived from run counts and active progress. Terminal
counts ensure the token changes when work finishes, even if the active run count is unchanged.
This is a snapshot token, not a durable event cursor or an exactly-once notification mechanism.
Use token changes to invalidate related application data; establish a baseline on first load.

Poll this summary once every five seconds while active, stop polling when idle, and pause
in hidden tabs, and honor `Retry-After`. Load individual runs and item results through the paginated
endpoints only when needed. Do not drain the full run inventory on every polling interval.

## Run representation

Lists and individual run reads use the same fixed shape:

```json theme={null}
{
  "id": "44444444-4444-4444-8444-444444444444",
  "retry_of_run_id": null,
  "type": "invoice_import",
  "resource": { "type": "invoice_import", "id": "33333333-3333-4333-8333-333333333333" },
  "status": "running",
  "progress": {
    "total": 1,
    "queued": 0,
    "running": 1,
    "completed": 0,
    "skipped": 0,
    "failed": 0,
    "cancelled": 0
  },
  "created_at": "2026-09-08T12:00:00.000Z",
  "updated_at": "2026-09-08T12:00:02.000Z",
  "started_at": "2026-09-08T12:00:02.000Z",
  "finished_at": null,
  "failure": null
}
```

`resource` identifies the business operation owning the run. Progress counts **admitted items**,
not invoices ultimately created or provider records filtered out during discovery. `total` is null
while discovery can add items. Once the collection closes, total equals the sum of the six counts.
A total of zero is a legitimate empty operation. Counts and parent status describe the same point
in time; progress changes update the parent's `updated_at`. All timestamps are UTC.

| Status                  | Meaning                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
| `queued`                | Admitted, execution has not started                                                                    |
| `running`               | Discovery or item processing is underway                                                               |
| `completed`             | Collection closed and all items completed or were legitimately skipped; includes empty runs            |
| `completed_with_errors` | Collection closed, all items settled, and successful/skipped items coexist with failed/cancelled items |
| `failed`                | Operation-level failure, or unsuccessful items with no successful/skipped items                        |
| `cancelled`             | Remaining work was stopped; already completed item results remain available                            |

One item failure does not terminate its siblings. Completion requires both a closed collection and
no queued/running items. A fatal discovery failure remains a parent failure even if admitted items
succeed. A failed run has `failure: { code, message }`; item-specific details remain on the items.
Reading a failed run or item returns HTTP 200. `finished_at` is set for terminal states.

## Items and results

```json theme={null}
{
  "id": "66666666-6666-4666-8666-666666666666",
  "run_id": "44444444-4444-4444-8444-444444444444",
  "retry_of_item_id": null,
  "resource": { "type": "invoice_import", "id": "33333333-3333-4333-8333-333333333333" },
  "status": "completed",
  "result": {
    "type": "invoice_import",
    "outcome": "imported",
    "invoice_id": "55555555-5555-4555-8555-555555555555"
  },
  "failure": null,
  "created_at": "2026-09-08T12:00:00.000Z",
  "updated_at": "2026-09-08T12:00:30.000Z",
  "started_at": "2026-09-08T12:00:02.000Z",
  "finished_at": "2026-09-08T12:00:30.000Z"
}
```

Items use `queued | running | completed | skipped | failed | cancelled`. Pending items have null
result and failure. Failed items have a stable failure code/message and null result; cancelled items
have neither. Successful/skipped items retain a typed business result, available immediately
regardless of sibling or parent status. Historical items whose result was never retained have null
result.

For invoice imports, `imported` and `duplicate` results include an invoice UUID; `not_invoice` has
`invoice_id: null`. All three are completed processing outcomes. Financial inconsistency does not
fail the item: read the resulting invoice's `financially_valid` and `financial_errors`.

Results describe the exact item execution, remain unchanged after invoice edits or explicit retry,
and contain resource references rather than copies of business documents. Publication, item result,
applicable usage, and possible parent completion commit atomically. There is no run-level `/result`
or separate `/results` collection: use `/items`, optionally filtered to terminal statuses.

## Filtering and pagination

Run lists support `status`, `type`, `created_from` (inclusive UTC), `created_before` (exclusive UTC),
`resource_type`, `resource_id` (requires resource\_type), and `ids` (at most 100 UUIDs).
`status`, `type`, and `ids` accept comma-separated values. Different filters use AND; values within
one filter use OR. Omit status to include terminal runs. Run order is `created_at DESC, id DESC`.
When both creation bounds are supplied, `created_before` must be later than `created_from`.

Item lists support comma-separated `status`. Order is `created_at ASC, id ASC`.

Both lists return `{ "data": [...], "next_cursor": null }`, default to 50 items and allow at most 100. Cursors bind to the organization, filters, ordering, and (for items) parent run. Continue with
the same filters. Pagination reads current state rather than a frozen snapshot. A null cursor means
there are no more matching rows **at that moment**, not that discovery or processing has finished.
Newly settled items can enter a status-filtered collection before an existing cursor: restart listing
when refreshing it. This is not an incremental event feed.

## Polling in an application

1. Share one summary query across application modules. Refresh it after successful admission and
   when returning to the app; use its change tokens to invalidate affected data.
2. Open run lists on demand, fetching one page at a time. Do not expand the background summary
   into a poll for every active run or every admission ID.
3. Poll the selected active run without a status filter so its terminal state remains visible.
   Fetch item/result pages only when the user opens them.
4. Stop polling unavailable IDs. Resolve 401/403; respect `Retry-After` across summary, focus, and
   detail requests on 429. Back off for transport/5xx failures and pause while hidden.

Polling reads stored product state without contacting the execution or extraction provider.
Responses use `Cache-Control: private, no-store`. The generated client owns no timers; your
application controls polling.

## Cancellation, retry, and recovery

Cancellation takes no body or idempotency key. It fences publication, closes discovery, and cancels
queued/running items before requesting provider cancellation. Completed/skipped/failed items retain
their results and failures. Repeated cancellation succeeds, including after a newer retry exists.
Other terminal runs return 409 `conflict`. A provider cancellation failure cannot reopen product work.

Retry remains a domain command: `POST /v1/invoices/imports/{id}/retry` validates sources and capacity
again and optionally accepts `Idempotency-Key` to safely replay that retry request. Explicit retries create new run and item IDs with
`retry_of_run_id` and `retry_of_item_id`; replays return the same new identities. Earlier runs/items
never reopen. Future bulk retry commands select unsuccessful items rather than redoing successes.
Automatic execution retries keep the current item identity and cannot overwrite terminal results.

If an execution stops outside the API, background recovery reconciles confirmed failure or
cancellation into the run and item. An execution that completes without publishing its business
result fails with `execution_incomplete`. Temporary provider unavailability does not change the
product state or cause an automatic resubmission. Retrieve the import's failure and evidence before
choosing an explicit retry.
