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

# Agreements

> Create agreements, add their prices and documents, and keep them up to date.

An agreement tells Watchdog what a supplier has agreed to: who it covers, when it applies, what
things cost, and the contract documents behind it. Watchdog matches invoices to your agreements and
checks them against the terms and prices. Reading needs an API key with **Read** access; everything
else on this page needs **Write**.

## Create an agreement

Look up the supplier and recipient IDs with `GET /v1/suppliers` and `GET /v1/recipients`, then create
the agreement:

```bash theme={null}
curl "$API_URL/v1/agreements" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Support services",
    "status": "active",
    "supplier_ids": ["<supplier ID>"],
    "effective_date": "2026-01-01",
    "expiration_date": "2026-12-31"
  }'
```

A few things to know:

* **Choose at least one supplier.** An agreement without suppliers matches no invoices. No
  recipients means any recipient.
* **Status** is `draft` unless you say otherwise. An active agreement needs a title. You can edit an
  agreement, its prices and its documents in every status, including `archived`.
* **Applicability** narrows which invoices the agreement covers, for example only invoices with a
  certain buyer reference or delivered to a certain country.
* **Instructions** give the checks extra context in plain language, such as which schedule to use
  when the documents disagree.

## Edit an agreement

`PATCH /v1/agreements/{id}` changes only the fields you send. Lists such as `supplier_ids` and
`tag_ids`, and objects such as `applicability`, replace the whole value, so send the complete list
or object.

To avoid overwriting someone else's change, send the `ETag` from `GET /v1/agreements/{id}` as
`If-Match`. A `412` means the agreement changed in the meantime: read it again and reapply your edit.

Saving never re-matches invoices or re-runs checks. After changing suppliers, recipients, dates or
applicability, [refresh the matched invoices](/api-reference/matching).

## Add prices

Send one or more prices as an array. A price is either a fixed amount or a rate, such as a fee of
7.5% of the invoice subtotal:

```bash theme={null}
curl "$API_URL/v1/agreements/$AGREEMENT_ID/price-items" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H 'Content-Type: application/json' \
  -d '{"entries": [
    {"description": "Support hour", "unit": "hour", "currency_code": "NOK",
     "price": {"type": "amount", "amount": "125.50"}},
    {"description": "Service fee",
     "price": {"type": "rate", "fraction": "0.075", "basis": "invoice subtotal"}}
  ]}'
```

Amounts are decimal strings, and rates are fractions: `"0.075"` means 7.5%. `PATCH` on the same path
changes up to 100 prices at once, sent the same way as `entries` with each price's `id`. Each request
succeeds or fails as a whole.

`GET …/price-items` lists the prices. Narrow it with `search`, or with `filters`, a JSON array of
conditions that must all match, and order it with `sort` and `direction`:

```bash theme={null}
curl --get "$API_URL/v1/agreements/$AGREEMENT_ID/price-items" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  --data-urlencode 'filters=[{"field": "valid_until", "operator": "less_than", "value": "2026-01-01"}]' \
  --data-urlencode 'sort=description'
```

The same parameters work for `GET …/price-items/export`, which downloads the prices as CSV, and as a
JSON body for `POST …/price-items/delete`, which removes every matching price however many there
are. To remove specific prices, send `{"ids": [...]}`.

## Upload and import a source

Contract documents and price lists are added in two steps. First upload the file as described in
[Upload invoices](/api-reference/upload-invoices#1-upload-the-file). Then add it to the agreement with
the `document_id` you got back:

```bash theme={null}
curl "$API_URL/v1/agreements/$AGREEMENT_ID/document-imports" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H 'Content-Type: application/json' -H "Idempotency-Key: $(uuidgen)" \
  -d "{\"entries\": [{\"document_id\": \"$DOCUMENT_ID\"}]}"
```

The optional `Idempotency-Key` prevents duplicate admission, so a retry never adds the file twice. Watchdog prepares the file in
the background and works out whether it holds the terms or a price list. You can change that with
`PATCH /v1/agreements/{id}/documents/{document_id}`. Checks read the terms documents. Prices from a
price list are only added when you import them.

## Import prices from a document

Watchdog can read the prices from one or more price lists for you:

```bash theme={null}
curl "$API_URL/v1/agreements/$AGREEMENT_ID/price-imports" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H 'Content-Type: application/json' \
  -d "{\"entries\": [{\"document_id\": \"$DOCUMENT_ID\", \"default_currency\": \"NOK\"}]}"
```

Each document gets its own price import, and they run together in one workflow run. This usually
takes a few minutes. Follow the run, or read `GET …/price-imports/{import_id}` until each import has
finished; one document failing doesn't stop the others.

* **Adding or replacing.** By default the new prices are added to the ones already there. Set `mode`
  to `replace_document` to replace the prices from earlier imports of the same document, or to
  `replace_all` to replace every price. If the import fails, nothing is removed.
* **Checking the result.** `GET …/price-imports/{import_id}/download` gives you every row Watchdog
  found and whether it was added, skipped as a duplicate or rejected.
* **Importing again.** Importing a document again with the same file and settings returns its
  existing import, marked `reused`.
* **Incomplete extraction.** If the import fails with `extraction_incomplete`, try again with
  `instructions` that say where the prices are, or with the `worksheets` that hold them.

## Handle renewals

Each agreement has a `renewal_action` that tells you what's due next, for example that it must be
cancelled by a certain date. List the agreements with a deadline coming up:

```bash theme={null}
curl --get "$API_URL/v1/agreements" \
  -H "Authorization: Bearer $API_KEY" -H "X-Organization-Id: $ORGANIZATION_ID" \
  --data-urlencode 'renewal_action_kinds=cancel_by,renew_by' \
  --data-urlencode 'renewal_deadline_through=2026-12-31' \
  --data-urlencode 'sort=renewal_deadline'
```

To record your decision, edit the agreement with `renewal_action`: `{"type": "cancel",
"expiration_date": "…"}` ends it on that date, and `{"type": "extend"}` extends it by one renewal
period. Like any edit, send the agreement's `ETag` in `If-Match`.

## Organize with tags

Create tags with `POST /v1/agreements/tags` and list them with `GET /v1/agreements/tags`. Assign them
with `tag_ids` when you create or edit an agreement, and filter agreements with `tag_ids`.

## Review suggested instruction edits

When someone dismisses an alert and says why, Watchdog may suggest a change to the agreement's
instructions so the same alert isn't raised again. List the suggestions with
`GET /v1/agreements/{id}/context-suggestions`, then accept or reject each one with
`POST …/context-suggestions/{suggestion_id}/accept` or `/reject`. Accepting edits the instructions;
rejecting makes sure the same edit isn't suggested again.

## See matched invoices and spend

Use the invoice endpoints with `agreement_ids`:

* `GET /v1/invoices?agreement_ids=$AGREEMENT_ID` lists the invoices matched to the agreement.
* `GET /v1/invoices/metrics?agreement_ids=$AGREEMENT_ID` counts them and totals the spend. Add
  `issued_from` and `issued_through` for a period.

Totals are given per currency. Don't add different currencies together.

## Delete an agreement

`DELETE /v1/agreements/{id}` removes the agreement. There is no undo. While Watchdog is still working
on the agreement, deletion returns `409`: wait a little, and try again.


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