
# 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](/docs/api/management/jobs/get-password-reset). For a single user use [Force a password reset](/docs/api/management/users/force-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 as `not_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_days` is 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.

```json
{
  "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

```bash
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`
