Create a bulk password reset job
POST /v1/jobs/password-resets · permission user-credentials:write
Forces many users to choose a new password in one asynchronous job, for example after a suspected leak or to move users off weak imported hash formats. The response returns the job at once; the users are flagged in the background. Follow the job with Get a bulk password reset job. For a single user use Force a password reset.
Select users in one of two ways (not both):
user_ids: up to 100,000 user ids; repeated ids count once. Ids that are not users of this environment, and users without a password, are counted asnot_found.hash_algos: every user whose password is stored with one of these algorithms and was last changed before the job was created (for example["md5", "phpass"]). A password set after the job was created is not selected.
The connection_id filter is not available yet; a request that sends it is refused with 422 validation_failed (not_supported at /connection_id).
Choose the mode:
on_login: each selected user must set a new password after their next successful password sign-in.lazy_then_bulk_reset: the same until the deadline (deadline_days, 1–365, default 30). At the deadline, the password hash of every selected user who has not changed their password yet is deleted and a password reset email is sent to their verified primary email address (users without one can still request a reset themselves).deadline_daysis only accepted in this mode.
Idempotency-Key is required. The same credential, key and request return the same job (with Idempotent-Replayed: true); the same key with a different request is refused with 422 idempotency_key_reuse. An environment can have at most 20 jobs that are not completed; a lazy job stays open until its deadline. The job creation is written to the management audit log (password_reset_job.created) and emits the password_reset.scheduled event.
Request
| Field | Type | Required | Description |
|---|---|---|---|
mode |
string | yes | on_login or lazy_then_bulk_reset. |
user_ids |
string[] | one of | 1–100,000 user ids (usr_…). |
hash_algos |
string[] | one of | Stored hash algorithms: argon2id, argon2i, bcrypt, scrypt, firebase_scrypt, pbkdf2_sha1, pbkdf2_sha256, pbkdf2_sha512, django_pbkdf2_sha256, django_bcrypt_sha256, phpass, ldap_ssha, ldap_ssha256, sha512_crypt, hmac_sha256, salted_sha256, salted_sha512, md5. |
deadline_days |
integer | no | lazy_then_bulk_reset only: 1–365, default 30. |
Response
202 Accepted with the job.
{
"id": "job_01h455vb4pex5vsknk084sn02q",
"mode": "lazy_then_bulk_reset",
"filter": "hash_algo",
"hash_algos": ["md5", "phpass"],
"status": "marking",
"deadline_at": "2026-02-14T09:30:00Z",
"total": 0,
"marked": 0,
"not_found": 0,
"passwords_deleted": 0,
"emailed": 0,
"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 malformed, the body is not valid JSON, or it has an unknown field. |
| 401 | unauthenticated |
The API key is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The key lacks the user-credentials:write permission. |
| 409 | conflict |
The environment already has 20 jobs that are not completed. |
| 413 | payload_too_large |
The body is larger than 4 MiB. |
| 422 | validation_failed |
A field is missing or invalid; errors[].pointer names it. |
| 422 | idempotency_key_reuse |
The Idempotency-Key was used with a different request. |
| 429 | rate_limited |
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/password-resets \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"mode": "lazy_then_bulk_reset", "hash_algos": ["md5", "phpass"], "deadline_days": 30}'
CLI: keenauth jobs password-resets create --mode lazy_then_bulk_reset --hash-algos md5,phpass --deadline-days 30 --yes