delete_orgs_bulk
Calls DELETE /api/orgs/bulk as the signed-in person · REST reference
Soft-delete several organizations as one all-or-nothing deletion, or preview it
The organizations are planned, guarded and deleted as ONE set in ONE DAL transaction — all of them or none. Each cascades exactly as DELETE /api/orgs/{id} does. The guard judges the SET: it refuses (409) any deletion that would leave no live holder of the PFF_SUPERADMIN role, including one where the orgs are each safe to delete alone but hold every superadmin between them. ?dryRun=true answers the same shape with no write — and a refused plan is then a 200 with blocked: true and the reason, which is how the portal shows it before confirming. Requires execute:DeleteOrgCommand. The caller's own token is forwarded. Registered BEFORE DELETE /api/orgs/{id}, so the literal bulk segment is not captured by the param route.
Arguments
| Argument | Type | Description | |
|---|---|---|---|
dryRunquery | string | The string "true" previews the deletion without performing it. Compared as a string, so ?dryRun=1 deletes. | |
bodyJSON | object | required | The request body, as a JSON object. Its fields are listed below. |
Fields of body
| Field | Type | Description | |
|---|---|---|---|
orgIds | array of string (uuid) | required |
Example call
A client sends this over the MCP session; Claude and other agents build it for you. The values are placeholders — the shape is real.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "delete_orgs_bulk",
"arguments": {
"body": {
"orgIds": [
"11111111-1111-4111-8111-111111111111"
]
}
}
}
}Result
On success (HTTP 200) the tool returns one text item holding the API's JSON, pretty-printed.
What was deleted — or, with dryRun, what would be.
{
"blocked": true,
"blockedReason": "string",
"dryRun": true,
"message": "string",
"reports": [
{
"blocked": true,
"blockedReason": "string",
"blockers": [],
"groupsRemoved": [],
"membershipsRemoved": 1,
"orgId": "string",
"orgName": "string",
"permissionsRetired": 1,
"rolesRemoved": [],
"usersDeactivated": [],
"usersKept": []
}
]
}Fields
| Field | Type | Description |
|---|---|---|
blocked | boolean | Always false when dryRun is false — a refused deletion answers 409. |
blockedReason | string | null | |
dryRun | boolean | |
message | string | |
reports | array of object | |
reports[].blocked | boolean | |
reports[].blockedReason | string | null | |
reports[].blockers | array of object | Every reason this organization may not be deleted yet; empty when it may. |
reports[].blockers[].amountCents | integer | null | The money involved, in cents, where there is one. |
reports[].blockers[].code | "LAST_SUPERADMIN" | "OPEN_CLAWBACK" | "PAYOUTS_PAUSED_BY_CLAWBACK" | "CLAWBACK_WINDOW_OPEN" | "BALANCE_OWED" | "PAYOUT_TRANSFER_UNSETTLED" | … (13 values) | |
reports[].blockers[].count | integer | How many of the thing the code names. |
reports[].blockers[].message | string | The sentence a person reads — what holds the deletion and what clears it. |
reports[].blockers[].until | integer | null | Epoch milliseconds the blocker lifts by itself, where it does (the clawback window). |
reports[].groupsRemoved | array of object | The organization's live groups — its OWNER and MEMBER groups among them; each is soft-deleted. |
reports[].groupsRemoved[].id | string | |
reports[].groupsRemoved[].name | string | null | |
reports[].membershipsRemoved | integer | Current (non-REMOVED) memberships into those groups; each is stamped REMOVED. |
reports[].orgId | string | |
reports[].orgName | string | |
reports[].permissionsRetired | integer | The organization's per-org permissions. Not written: every grant path requires a live organization, so they confer nothing once it is deleted. |
reports[].rolesRemoved | array of object | The organization's own live roles (its OWNER and ORG_USER roles); each is soft-deleted. |
reports[].rolesRemoved[].id | string | |
reports[].rolesRemoved[].name | string | null | |
reports[].usersDeactivated | array of object | Members who belong to no other live organization. Each is soft-deleted and their DynamoDB auth row suspended, so they cannot sign in by either path. |
reports[].usersDeactivated[].email | string | null | |
reports[].usersDeactivated[].id | string | |
reports[].usersKept | array of object | Members of another live organization. Kept, and lose only this organization's memberships; keptBy names the organization(s) that keep them. |
reports[].usersKept[].email | string | null | |
reports[].usersKept[].id | string | |
reports[].usersKept[].keptBy | array of object |
When it fails
A failed call returns isError: true with the API's message as text. A change the person declines, or lets time out, also comes back this way and nothing changes.
400orgIdsabsent, empty, not an array, holding a non-string — or holding an id that is not a UUID.403Authenticated, but the caller holds none of the required permissions.
404An organization in the set does not exist or is already deleted; nothing was deleted.
409The guard refused — the deletion would leave no live PFF_SUPERADMIN holder — or the graph changed between the plan and the write. Nothing was deleted.
What the model is shown
This is the tool exactly as the server lists it in tools/list. The description starts with the REST call it makes.
{
"name": "delete_orgs_bulk",
"description": "DELETE /api/orgs/bulk — The organizations are planned, guarded and deleted as ONE set in ONE DAL transaction — all of them or none. Each cascades exactly as `DELETE /api/orgs/{id}` does. The guard judges the SET: it refuses (409) any deletion that would leave no live holder of the `PFF_SUPERADMIN` role, including one where the orgs are each safe to delete alone but hold every superadmin between them. `?dryRun=true` answers the same shape with no write — and a refused plan is then a 200 with `blocked: true` and the reason, which is how the portal shows it before confirming. Requires `execute:DeleteOrgCommand`. The caller's own token is forwarded. Registered BEFORE `DELETE /api/orgs/{id}`, so the literal `bulk` segment is not captured by the param route.\n\nThis changes data. The person is asked to confirm it before it runs, and nothing happens if they decline.",
"inputSchema": {
"type": "object",
"properties": {
"dryRun": {
"type": "string",
"description": "The string \"true\" previews the deletion without performing it. Compared as a string, so `?dryRun=1` deletes."
},
"body": {
"type": "string",
"description": "The JSON request body: { orgIds*: array of string }"
}
},
"required": [
"body"
]
},
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": true,
"openWorldHint": false
}
}