Skip to main content
An integration that creates clients, matters, folders, documents, drafts, comparisons, matrices, reviews and workflow runs needs to be able to undo that. This page is how.

The rule, in one line

A delete removes what you named and refuses anything that would take something else with it. That means “delete an empty thing” for a container and “delete this thing” for a leaf. It is deliberately conservative: your real need is to undo what you just made, and you can always empty a container first. A single DELETE that quietly removed a hundred documents, their stored files and their vectors is not an operation anybody can safely retry.

Deleted means deleted

Some resources go to a trash a person can see in the app. Drafts and comparisons do; documents, clients, matters, folders, reviews, workflow runs and matrices do not. From this API the two are indistinguishable, on purpose. A deleted resource answers 404 on its own GET, disappears from its own list, and stops being exportable. Nothing here brings it back. There is no ?hard=true, no deletedAt field and no restore operation, and there will not be: an irreversible variant of something whose reversible variant you cannot observe is a footgun with no upside. Each delete’s own description in the Workspace Reference says whether a colleague could still restore it in the app.

Delete is not idempotent

A DELETE that removed a row answers 204. Repeating it answers 404, because we report what actually happened rather than assuming success. PostgREST reports success for a delete that matched nothing, so “always 204” would mean answering 204 for a mistyped id too.
On a delete that timed out, treat a subsequent 404 as success. That is the only place this matters, and it is the whole reason it is written down.
There is no Idempotency-Key on a delete.

What refuses, and why

Every refusal is a 409, never a 403. On this API a 403 means your credential is missing a scope or your installation is inactive, and both send you to rotate a credential. A refusal about the state of a row must not read like that.
Read blockers. It names every reason and how many of each, so you can clear them in one pass rather than discovering them one at a time.
Poll operationId when the problem carries one, then repeat the delete. It is absent when the work was started in the app, because there is no operation of ours to poll; read the resource’s own status instead.There is no way to cancel work through this API. A delete that killed a running job would be a second way to spend your money and lose the result.
Every organization has exactly one, minted at signup, and it cannot be deleted here or in the app. Emptying it will not help. Create matters explicitly with POST /v1/matters rather than writing into the default one, and a matter your integration created can be emptied and deleted.

Per resource

Playbooks cannot be deleted through this API. There is no playbooks.delete operation, and a DELETE against a playbook path answers 405 method-not-allowed. A playbook you no longer want can be emptied with playbooks.update, or deleted by its owner in the app. The gap is deliberate for now: a review records the playbook it ran against as a plain JSONB key with no foreign key, so nothing in the database stops a deletion from leaving twelve completed reviews pointing at a row that is gone.

Deleting a document reaches further than the document

This is the one delete with a blast radius worth reading before you loop over it. Removing a document also removes:
  • its extracted text, its versions and its stored original;
  • its highlights, flags, obligations and summary;
  • its entries in the matter’s chronology;
  • its row, and that row’s answers, in every document matrix it appears in.
That last one changes the shape of a matrix somebody already finished, with no event anywhere. It is the same behaviour as the delete button in the app; what is new is that a loop can reach it.

Scopes

Most deletes carry the resource’s write scope. Three do not, because the scope taxonomy has no write scope for them: :run reads as “starting work” and these are deletes. The taxonomy is a frozen contract, and adding compare:write would make the operation answer 403 for every credential already issued, including yours. If you are minting a credential for a cleanup job, grant the :run scope for those three.

A worked cleanup

Undoing everything one run created, in the order the refusals allow:
1

Delete the runs and results

comparisons.list, reviews.list, workflowRuns.list and matrices.list inside the matter, then delete each. Anything still running answers 409 operation-in-flight; poll it and come back.
2

Delete the drafts and documents

drafts.list and documents.list, then delete each. Abort any upload session you left open with DELETE /v1/uploads/{uploadId}; find them with GET /v1/uploads?status=in_progress.
3

Delete the folders, deepest first

GET /v1/folders?parentId= walks one level down. A folder with a child folder answers 409 resource-not-empty, so clear the leaves first.
4

Delete the matter, then the client

The matter refuses while anything is left, and names what. The client refuses while it owns a matter.
Read errors for the full problem-document shape.
Last modified on August 24, 2026