Skip to main content
These rules apply to every endpoint. The endpoint reference has the exact fields, parameters and responses for each one.

Choose an organization

Every request sends your API key as Authorization: Bearer … and the organization it’s for as X-Organization-Id, as in the quickstart. To see which organizations your key can use, call GET /v1/organizations, the one request that doesn’t need X-Organization-Id.

Access levels

A key has one of three levels: Read lets you read data, Write also lets you create, change and start work, and Admin adds organization settings. A key can never do more than its owner’s role in the organization allows. GET /v1/organization shows what your key can do there. See authentication and API keys for more.

Values

  • Amounts, quantities and rates are decimal strings, such as "1250.00". Rates are fractions: "0.25" means 25%. Amounts are exact as recorded; amounts converted to the organization currency are rounded to cents.
  • Dates are YYYY-MM-DD. Timestamps are in UTC.
  • A value Watchdog doesn’t know is null.

Pages of results

Lists return up to 50 records by default (limit goes up to 1000), plus a next_cursor. To get the next page, send it back as cursor with the same filters. When next_cursor is null, you have everything. Reads accept fields to return only the fields you need, comma-separated, with dots for nested fields: fields=title,impact_amount,invoice.invoice_number. On lists it applies to each row in data. id (or a group’s key) is always returned, and an unknown field returns 400 with the fields that exist. Response types still describe the full record; leave fields out to get it. Lists use cursor and limit, with sort and direction=asc|desc. There is no offset pagination. Free-text filters are called search; search commands take query. Multi-value filters have plural names, such as statuses, and accept comma-separated values. Use supplier_ids=A,B&supplier_operator=is_not to exclude values. is is the default. Supported relation filters combine with join_operator=and|or; the reference describes which ones participate. Organization and access boundaries always apply. Calendar dates use inclusive _from/_through; timestamps use _from/_before. has_topic=false and has_claim=false select missing relationships; topic_ids=A,null includes a topic and uncategorized alerts together.

My records and teams

Lists of invoices, agreements, alerts and claims accept mine=true, which returns only the records covered by the teams you belong to. To use specific teams instead, send team_ids. Teams only narrow what you see; they never give you access to more. See teams to set them up.

Safely retrying requests

Requests that create something or start work accept an optional Idempotency-Key header. The endpoint reference shows which ones. If a request times out or you don’t get an answer, send it again with the same key and the same body. You get the original result back instead of a second copy. No endpoint requires a key. Other commands are safe to repeat: setting a state that already holds, or removing something already gone, changes nothing.
Use a new key for each new action. Reusing a key with a different body returns 409.

Avoid overwriting someone else’s change

Resources that support conditional updates return an ETag header. Send it back as If-Match when you change or delete the record. If someone changed it in the meantime, you get 412 and can read it again before retrying. If-Match is optional.

Commands and deletion

Background work returns 202, a workflow_run_id, and a Location pointing to that run. Cancel and retry its original inputs through /v1/workflow-runs; use item_ids to select items. Checking with no eligible invoices returns 200 and a null workflow ID. Checking all agreements can start several runs: its workflow_run_ids lists them and Location points to the first. Invoice import creation keeps its import resource Location; the returned import includes the workflow ID. Delete a record with DELETE /{collection}/{id}; send options in the query string. Bulk commands use POST /{collection}/{verb} with {ids} or {filter}. Creation batches use {entries}. A preview is POST {command}/preview with the same input as the command. Read comments with GET /v1/activity?entry_type=comment.

Errors

Errors look like this:
  • Use code in your code, not message. Validation errors also list the fields to fix in details.
  • Fix 400, 401 and 403 errors before trying again. Retrying won’t help.
  • On 429, you’ve sent too many requests. Wait the number of seconds in Retry-After.
  • On 5xx or a network error, wait a moment and try again, waiting longer each time.
  • Include request_id when you contact us about an error.
Work that runs in the background, such as an import, can fail after the request succeeded. That failure shows up on the import or workflow run, not as an HTTP error.

Changes to the API

The API has one version, /v1, and it is in beta. Until we release a stable version, endpoints, fields and behavior may change or be removed without notice. We announce the stable version in the changelog. The API also grows over time: we add new endpoints, fields, optional parameters and values without notice, during the beta and after it. Build your integration to:
  • ignore fields it doesn’t know;
  • treat a value it doesn’t recognize, such as a new status, as “other”.
Once the API is stable, a change that could break an integration is marked as deprecated in the endpoint reference, announced in the changelog, and emailed to the customers who use it. It keeps working for at least 3 months after that.