Delete a project. With no body it deletes a project that holds
nothing, and refuses one that does; mode is how you say what
should happen to what it holds.
Empty (no body): the project is removed only while it holds no
resources. A project that holds any is refused with 409
project_not_empty, and the refusal enumerates what is inside:
error.details.resource_count is the total, and
error.details.contents names each kind with a count and up to five
names. Every project row also carries that total as
resource_count, so an application can find the projects it may
delete in one list call. Records of work never hold a project open
and are not counted.
Detach (mode: detach): only the grouping row is removed; every
resource in the project moves to the implicit Default project, where
it stays readable and is listed by ?project_id=default. Refused
with 409 project_has_scoped_credentials while an API key, service
account, vault credential, webhook endpoint, member grant or pending
invite is scoped to the project, because detaching any of those
would silently widen it.
Purge (mode: purge with confirm equal to the project’s
name): available only on an ARCHIVED project, because a teardown
needs a state you can sit in and reverse first; a live project is
refused with the coded 409 project_not_archived. Archive the
project, confirm it is the one you mean, then purge. The project is
removed WITH its contents in one transaction: the resources it holds
and its scoped webhook endpoints and vault credentials are deleted,
API keys and service accounts pinned to the project are revoked, and
member grants and pending-invite scopes on the project are cleared.
Resources with a lifecycle and a delete of their own, such as
stores, hosted APIs and files, are not removed: they move to the
Default project, and because they do hold a project open, an
unqualified delete refuses while any of them is in the project.
Refused with 409 while a resource that must be released first is
still attached (the refusal names it), while a member’s only project
grant is this one, or while a live invite carries only this project
(clearing either would widen that person to the whole workspace, the
invite one acceptance earlier).
A purge is recoverable for 30 days. The project disappears from
every list and read immediately, and its name is freed for reuse,
but the project and its resources are kept and permanently deleted
only once the window closes. POST /v1/projects/{project_id}/restore brings the project and its
resources back inside that window; the credentials the purge revoked
and the grants it cleared stay that way.
The 409 carries the blockers under error.details.blockers (kind,
typed id, name, and the blocks modes each refuses), their
total under error.details.blocker_count, and, for existing
clients, the same rows under error.details.credentials. The lists
are capped at 50 rows; the counts are not, and the refusal is
decided on the count.