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 - conversations, runs, calls, callers, memories
- 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. This is what
an unqualified delete used to do silently. 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. Agents
(with their tests), knowledge bases (with their documents), tools,
audio assets, scoped webhook endpoints and scoped vault credentials
are deleted; API keys and service accounts pinned to the project are
revoked; member grants and pending-invite scopes on the project are
cleared. The runs and conversations of a deleted agent go with that
agent, transcripts and recordings included, and stop being readable
the moment the purge lands - a detach leaves them alone, because a
detached project’s agents are still live. Callers, suite runs and
memories are operational records and survive either mode, moving to
the Default project; a batch call survives the project the same way,
but not the agent it dialled for, so a purge’s agents take their
campaigns with them once they are permanently removed. External brains,
skills, teams, channels, stores, hosted APIs and files are resources
with their own lifecycle and their own delete, so a purge does NOT
remove them either: they move to the Default project too, the teardown
preview lists every one of them under retains, 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 phone number is attached
(release or move it first), 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). Preview either mode with
GET /v1/projects/{project_id}/teardown.
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.