delete_users_by_id

Changes data · you confirm first Idempotent

Calls DELETE /api/users/{id} as the signed-in person · REST reference

Flag a user as deleted

A SOFT DELETE, and the wording of the response says so. DeleteUserCommand sets deleted/deletedAt/deletedBy on the :User node and removes nothing — no node, no edge, no property. NEVER DELETE DATA is a platform rule, not a preference of this route. WHAT THE FLAG DOES. Both of the reads that matter exclude it, and both are null-safe (coalesce(u.deleted, false) = false, so a node carrying no deleted property at all is still returned): ListUsersQuery, so the row leaves GET /api/users; and GetUserByEmailQuery, which is the lookup behind OTP verification — so POST /api/auth/login cannot resolve the account and answers 401. The same command SUSPENDS the user's DynamoDB auth row, so the password paths refuse them too; a failure there rolls the flag back. A user with no auth row is deleted all the same. REFUSED (409), writing nothing, when the user owns an organization (every org has exactly one owner; transfer ownership first) or is the last live holder of the PFF_SUPERADMIN role. The message names what to fix first. orgId is a REQUIRED QUERY PARAMETER for the same reason it is required on the create and the update: the DAL declares deleteUser(orgId: UUID!) and evaluates the write against that scope — the caller must hold execute:DeleteUserCommand IN that org, and the user being deleted must be a MEMBER of it unless it is the platform org (403 otherwise). A permission held in one org does not reach another org's users. No membership moves. deletedBy is the AUTHENTICATED CALLER and is never taken from the request. The DAL declares it UUID! and it is the audit actor; a client-supplied one would let the caller sign someone else's name to the deletion.

Arguments

ArgumentTypeDescription
idpathstring (uuid)requiredUser id. Not format-validated by this route.
orgIdquerystring (uuid)requiredNon-empty string, the DAL's permission scope. Not validated as a UUID by this route.

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.

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "delete_users_by_id",
    "arguments": {
      "id": "11111111-1111-4111-8111-111111111111",
      "orgId": "11111111-1111-4111-8111-111111111111"
    }
  }
}
Confirmation
The client shows the person the exact request and waits. Nothing runs until they tick Make this change. See Reading and changing.

Result

On success (HTTP 200) the tool returns one text item holding the API's JSON, pretty-printed.

The user is flagged as deleted. The message names the flag rather than claiming a removal, because nothing was removed.

json
{
  "message": "User flagged as deleted"
}

Fields

FieldTypeDescription
message"User flagged as deleted"

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.

400

orgId missing from the query string.

403

Authenticated, but the caller holds none of the required permissions.

404

No such user, or the user is already flagged as deleted.

409

Refused: the user owns an organization — every org has exactly one owner, so the owner is not deleted until ownership is transferred (or the organization is deleted with them) — or is the platform's last live PFF_SUPERADMIN. The DAL's own message, naming the organization or the role.

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.

json
{
  "name": "delete_users_by_id",
  "description": "DELETE /api/users/{id} — A SOFT DELETE, and the wording of the response says so. `DeleteUserCommand` sets `deleted`/`deletedAt`/`deletedBy` on the `:User` node and removes nothing — no node, no edge, no property. NEVER DELETE DATA is a platform rule, not a preference of this route. WHAT THE FLAG DOES. Both of the reads that matter exclude it, and both are null-safe (`coalesce(u.deleted, false) = false`, so a node carrying no `deleted` property at all is still returned): `ListUsersQuery`, so the row leaves `GET /api/users`; and `GetUserByEmailQuery`, which is the lookup behind OTP verification — so `POST /api/auth/login` cannot resolve the account and answers 401. The same command SUSPENDS the user's DynamoDB auth row, so the password paths refuse them too; a failure there rolls the flag back. A user with no auth row is deleted all the same. REFUSED (409), writing nothing, when the user owns an organization (every org has exactly one owner; transfer ownership first) or is the last live holder of the `PFF_SUPERADMIN` role. The message names what to fix first. `orgId` is a REQUIRED QUERY PARAMETER for the same reason it is required on the create and the update: the DAL declares `deleteUser(orgId: UUID!)` and evaluates the write against that scope — the caller must hold `execute:DeleteUserCommand` IN that org, and the user being deleted must be a MEMBER of it unless it is the platform org (403 otherwise). A permission held in one org does not reach another org's users. No membership moves. `deletedBy` is the AUTHENTICATED CALLER and is never taken from the request. The DAL declares it `UUID!` and it is the audit actor; a client-supplied one would let the caller sign someone else's name to the deletion.\n\nThis changes data. The person is asked to confirm it before it runs, and nothing happens if they decline.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "id": {
        "type": "string",
        "description": "User id. Not format-validated by this route."
      },
      "orgId": {
        "type": "string",
        "description": "Non-empty string, the DAL's permission scope. Not validated as a UUID by this route."
      }
    },
    "required": [
      "id",
      "orgId"
    ]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": true,
    "openWorldHint": false
  }
}