<!-- section: Auth API · status: building · source: docs/auth-api/05-reset-password.md -->

> The forgotten-password flow, enumeration-resistant at both ends.


# Reset a password

Two calls: ask for a link, then use it. Both take a **publishable** key.

**This is also how somebody sets a password for the first time.** Accounts are
created with an emailed code and have no password until one is chosen, and this
flow works the same either way — it proves the address, then sets what they
typed. There is no separate "add a password" call to integrate.

## POST /v1/auth/request-password-reset

```bash Request
curl -X POST "$BASE/v1/auth/request-password-reset" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -d '{"email":"ada@example.com","redirectTo":"https://yourapp.com/new-password"}'
```

```json Response
{ "status": true, "message": "If this email exists in our system, check your email for the reset link" }
```

**An address that does not exist gets the same `200` and the same message**, and
takes a similar amount of time to answer. A faster "no such account" is an
enumeration oracle even when the words are careful.

The mail goes out under your project's name. The link is single-use and expires
in an hour.

**`redirectTo` must be an origin you registered.** A reset link that could land
anywhere is an open redirect with a valid session attached, so an unregistered
origin is refused with `INVALID_REDIRECT_URL`.

## POST /v1/auth/reset-password

Your page reads the `token` from the query string and posts it with the new
password.

```bash Request
curl -X POST "$BASE/v1/auth/reset-password" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -d '{"token":"…","newPassword":"a-new-long-password"}'
```

```json Response
{ "status": true }
```

**Every other session that person had is revoked.** A reset is what someone does
when they think their password is known to a stranger, and leaving that
stranger's session alive would make the reset cosmetic.

## Users with no password

Someone who only ever signed in with Google or a passkey has no password to
reset. Requesting one still answers `200` — saying otherwise would disclose how
they sign in — but no mail arrives, because there is nothing to reset.

Sending them through `request-password-reset` is, however, how you give them
one: the flow sets a password on an account that had none.

## Errors

| Status | Code | Means |
|---|---|---|
| `400` | `INVALID_TOKEN` | Expired, already used, or malformed |
| `400` | `PASSWORD_TOO_SHORT` | Under 12 characters — see [settings](/docs/auth-api/settings) |
| `403` | `INVALID_REDIRECT_URL` | `redirectTo` is not a registered origin |
| `429` | — | Rate limited; see [limits](/docs/auth-api/limits) |
