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

# Authentication and conventions

> Connect to the preview API and handle permissions, pagination, idempotency, and errors.

These conventions apply to the [API preview](/api-preview/introduction). Generated endpoint pages
contain the exact fields and operation-specific responses.

## Authentication and permissions

Use `GET /v1/organizations` without an organization header to discover your permitted organizations.
See [organizations, users, and teams](/api-preview/teams) for the complete flow.
For resource requests, send your personal API key as a bearer token and target one organization explicitly:

```bash theme={null}
curl --fail-with-body "$API_URL/v1/organizations/$ORGANIZATION_ID" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Organization-Id: $ORGANIZATION_ID"
```

`GET /v1/me` identifies the key and owner without an organization header.
Organization detail returns `access.role`, `access.configured_level`, and `access.effective_level`. Effective access is the lower of the configured level and the owner's
current organization role. Members have Write; organization admins have Admin.

| Operation                                                                                              | Required level |
| ------------------------------------------------------------------------------------------------------ | -------------- |
| Read invoices, documents, selectors, filters, imports, and public workflow status; authorize downloads | Read           |
| Create, upload, import, update, delete, restore, retry, or cancel                                      | Write          |
| Explicit organization-admin operations                                                                 | Admin          |

Write includes Read; Admin includes Write. There are no separate related-resource grants.
New keys default to Read and all organizations you have access to. All-organization access follows current and
future memberships; selected access stays within the selected set. Existing organization keys are
migrated to Write and remain restricted to their original organization, which is also their
fallback when the header is omitted.

API keys read membership, role, and account status from Watchdog’s database on each request.
Clerk changes take effect after webhook synchronization. Key edits and revocation are immediate.

Normal Clerk sessions use the active organization; a supplied organization header must match it.
Read [authentication and API keys](/api-reference/authentication) for account, MFA, and lifecycle rules.

## Values and collections

* Business resource IDs are UUIDs; organization and user IDs are opaque strings; JSON field names use `snake_case`.
* Amounts, quantities, and rates are decimal strings. `"0.25"` means a rate of 25%.
* Calendar dates use `YYYY-MM-DD`; timestamps are UTC. Unknown source values remain `null`.
* Text values must not contain null characters (`\u0000`).
* Collections return `{ "data": [...], "next_cursor": null }`. The default `limit` is 50; the maximum is 100.
* Pass `next_cursor` as `cursor` with the same filters and ordering. Stop when it is `null`.
* A cursor is a position in current data, not a frozen snapshot or an event subscription.

Query filters that accept several values use comma-separated values. Different filters combine
with AND; values within one filter combine with OR. See each endpoint for its supported filters.
Invoice metrics use the same filters as invoice lists and separate monetary totals by currency.
Invoice date ranges include both endpoints; the end date must be on or after the start date.
Workflow creation ranges exclude `created_before`, which must be later than `created_from`.
Reversed or empty exclusive ranges return `400` with the field to correct.

Requests are JSON, except file bytes sent directly to the returned storage upload URL. The complete
JSON body is limited to 2 MiB, including line data. Support contact requests allow up to 32 MiB
to include attachments; individual fields also have documented limits.
Responses use fixed shapes with paginated relationships, rather than arbitrary field expansion.

## My records and teams

Create shared teams, author predicates, and manage membership using the
[team management guide](/api-preview/teams).

Use `mine=true` on invoice lists and metrics, agreement lists, alert lists/metrics/groups,
and claim lists and metrics. It matches the shared teams the current user belongs to in the target
organization; personal API keys use their owner's memberships. Personal subscriptions are excluded.
This filters existing access and grants no additional permissions.

Each team's saved AND/OR rules are preserved. Teams combine with OR; other query filters narrow
results with AND. Alternatively, select up to 50 shared teams using comma-separated `team_ids`.
Combining `mine=true` and `team_ids` returns `400`. Omitted `mine` or `mine=false` adds no membership
filter; explicit `team_ids` still applies.

Invoice rules use the invoice's own supplier, recipient and fields, and matched agreements for
agreement rules. Agreement rules use linked parties and the agreement's fields, and matched invoices
for invoice rules. Alerts use their own supplier, invoice recipient and fields, and their own
agreement. Another agreement matched to the same invoice does not contribute to an alert's agreement
rules. Claims match through their agreement, including empty claims; their totals retain complete
claim membership and refunds. This mapping also applies to explicit `team_ids`.

No memberships or saved rules produces empty results and zero metrics. Missing, foreign, or personal
explicit team IDs return `404`; invalid saved rules return `409 conflict` with an instruction to fix
the team filters. This replaces silent-empty results for invalid rules.

Keep the same query, organization and user when following a `mine` cursor. Memberships, rules and
relative UTC dates are evaluated on each request, so changes can affect later pages. Pagination
remains live; no snapshot or scope version is stored.

## Safely retrying requests

Agreement creation, price-item creation, invoice creation, import creation, and explicit import retry accept an **optional** `Idempotency-Key`.
You can make normal requests without it. To safely retry after a timeout or lost response, choose
one unique value for the action (such as a UUID) and send the same value with the same input on each
attempt:

```bash theme={null}
export REQUEST_KEY=$(uuidgen)
curl "$API_URL/v1/invoices" \
  -H "Authorization: Bearer $API_KEY" \
  -H "X-Organization-Id: $ORGANIZATION_ID" \
  -H "Idempotency-Key: $REQUEST_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @invoice-input.json
```

Keep `REQUEST_KEY` for retries of this action. Use a new value for a new action. Without a key,
repeated requests are separate actions and do not replay earlier responses. The invoice duplicate
guard still applies: structured duplicates return `409`, and imports can complete as `duplicate`.
Concurrent import requests may start separate extraction jobs before the duplicate is known.

| Response | Meaning                                                                                                    |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| `201`    | A new synchronous resource was created                                                                     |
| `202`    | Import admission was confirmed; `Location` identifies the import                                           |
| `200`    | A read, update, or identical replay succeeded; a known file duplicate can also return the existing invoice |
| `409`    | A committed key was reused with different input, or the operation conflicts with current state             |

Validation failures return `400` without committing a creation receipt. Correct the input and reuse
that key. Editing an invoice never changes its creation receipt. Retrying an import creates new
run/item identities; replaying the retry request returns those same identities. If a resource was
permanently deleted through the legacy application, replay returns `409`. That key remains reserved;
use a new key for a new action. Soft-deleted invoices still replay normally.

## Conditional agreement edits

Individual agreements, prices, and document relationships return a strong `ETag`. Send that
resource's validator in `If-Match` on PATCH or DELETE to avoid overwriting a concurrent change.
A stale or weak validator returns `412 precondition_failed`; read the current resource before
reapplying the edit. Without the header, the edit applies to the locked current state.

Agreement PATCH preserves omitted fields; null clears nullable fields. A supplied nested object or
membership array replaces that complete field. Agreement list and detail responses include all
selected suppliers and recipients; send the complete desired ID array when changing a selection.
Successful nested deletion is `204`. Creation returns `201` with `Location`; updates and replays
return `200`.

## Errors and retries

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "The request could not be validated.",
    "request_id": "example-request-id"
  }
}
```

Validation errors may include bounded `details` with fields to correct. Branch on `error.code`,
not the human-readable message. Save the request ID for troubleshooting; you can send
`X-Request-Id` to supply your own correlation ID.

Correct `400`, `401`, and `403` failures before retrying. A resource outside your authorized
organization is unavailable. Respect `Retry-After` on `429`; back off for network errors and `5xx`
responses. A failed import admission may retain an `error.import_id`:
retrieve it before deciding whether to explicitly retry. Do not assume an error means no work exists.

A failed workflow run or item is still a retrievable resource: its GET returns `200`, with failure
information in its body. Follow the [polling guide](/api-preview/workflow-runs#polling-in-an-application)
for asynchronous work.
