Create a bulk user deletion job
POST /v1/jobs/user-deletions · permissions users:delete and jobs:write
Deletes up to 10,000 users in one asynchronous job. Each user goes through the same deletion as DELETE /v1/users/{user_id}: the user becomes pending_deletion and the permanent deletion runs afterwards; the deletion record notes that it came from a bulk job. Deletion cannot be undone, so the endpoint works in two steps:
- Send the request with
"dry_run": true. Nothing is written; the response holdsmatched_count, the number of users the request would delete. - Send the same selection with
"confirm_count"set to that number. If the number of matching users has changed in between, the request fails with409 confirmation_mismatchand no job is created; run the dry run again.
Select users in one of two ways: user_ids (repeated ids count once; results keep the order of first appearance) or organization_id (the organization's active and suspended members at the moment the job is created). Ids that do not match a user are kept in the job and reported as not_found.
Only one bulk user deletion job runs at a time in an environment; while one is running, a new job is refused with 429 rate_limited and Retry-After. The job is written to the management audit log when it is created and when it completes; each deleted user also has its own deletion audit entry. Follow the job with Get a bulk user deletion job and read per-user outcomes with List bulk user deletion results.
Request
Headers: Idempotency-Key (required, 16–64 characters A-Z a-z 0-9 _ -). Repeating the request with the same key and the same body returns the first job; the same key with a different body fails with 422 idempotency_key_reuse. The body may be up to 512 KiB.
| Field | Type | Required | Description |
|---|---|---|---|
user_ids |
array of strings | one of the two | User ids prefixed usr_, 1 to 10,000. |
organization_id |
string | one of the two | Organization id prefixed org_; at most 10,000 members. |
dry_run |
boolean | no | true: only count the users; nothing is written. Default false. |
confirm_count |
integer | yes, unless dry_run |
The matched_count of the dry run (0–10,000). |
Response
Dry run: 200 OK with {"dry_run": true, "matched_count": n}.
Job created: 202 Accepted with the count and the job.
{
"dry_run": false,
"matched_count": 2,
"job": {
"id": "job_01h455vb4pex5vsknk084sn02q",
"status": "running",
"filter": "user_ids",
"total": 3,
"succeeded": 0,
"not_found": 0,
"failed": 0,
"pending": 3,
"created_at": "2026-01-15T09:30:00Z"
}
}
Errors
Every error is an application/problem+json body with a stable code.
| HTTP | code |
When |
|---|---|---|
| 400 | malformed_request, unknown_field |
The Idempotency-Key header is missing or invalid, or the body is not valid JSON or has an unknown field. |
| 401 | unauthenticated |
The API key is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The key lacks users:delete or jobs:write. |
| 409 | confirmation_mismatch |
confirm_count is not the current number of matching users. |
| 409 | idempotency_in_progress |
A request with the same Idempotency-Key is still being processed. |
| 413 | payload_too_large |
The body is larger than 512 KiB. |
| 422 | validation_failed |
Both or neither of user_ids and organization_id, a malformed id, more than 10,000 users (too_many, too_many_users), an organization without members (no_users) or a missing confirm_count (required). |
| 422 | idempotency_key_reuse |
The Idempotency-Key was used with a different body. |
| 429 | rate_limited |
Another bulk user deletion job is running, or the rate limit was exceeded; honor Retry-After. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry later. |
Example
curl -X POST https://api.eu.keenauth.com/v1/jobs/user-deletions \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"organization_id": "org_01h455vb4pex5vsknk084sn02q", "dry_run": true}'
CLI: keenauth jobs user-deletions create --organization-id org_01h455vb4pex5vsknk084sn02q --dry-run --yes, then keenauth jobs user-deletions create --organization-id org_01h455vb4pex5vsknk084sn02q --confirm-count 42 --yes. Long id lists go in a file: --data @users.json.