> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Password reset and API-key revocation

> Walk a user through self-service recovery: the single-use reset link, token lifetimes, rate limits, failure modes, and the one-click API-key revocation that closes the perimeter after a reset.

# Password reset and API-key revocation

Orbit's recovery flow runs in two stages: a single-use link resets the
password, then a follow-up email offers a one-click revocation of every API
key the account created. This guide covers the whole lifecycle: what each
endpoint does, how long each link lives, what the revocation touches and
misses, and what lands in your audit log.

## What the recovery flow is for

Walk a user through this flow when:

* **A credential may have leaked.** A phished password, a reused password
  that appeared in a breach elsewhere: rotate the credential, then
  invalidate anything the old credential could have minted.
* **A device outlived its owner.** A departed member's laptop still holds
  sessions and, possibly, API keys. The reset ends the sessions; the
  revocation ends the keys.
* **A second factor or trusted device is lost.** Recovery is anchored on
  email ownership rather than the old device, so the member can get back
  in, then cut off everything the lost device still held.
* **You want a clean perimeter after an incident.** Even without a
  confirmed leak, reset plus revocation is the fastest way to expire every
  credential in the wild for that account.

The flow is self-service and tenant-owned: it runs on possession of the
user's inbox, so your operators stay in control of it and Orbit support
does not initiate resets on your behalf.

## The user journey

```mermaid theme={null}
sequenceDiagram
    actor User
    participant API as Orbit API
    participant Inbox as User inbox
    User->>API: POST /api/v1/auth/forgot-password
    API->>Inbox: Reset email, link valid 1 hour
    User->>API: POST /api/v1/auth/reset-password
    API-->>User: 200, every other session signed out
    API->>Inbox: Follow-up email, revocation link valid 24 hours
    User->>API: GET /api/v1/auth/post-reset-revoke-api-keys
    API-->>User: Confirmation page with the revoked-key count
```

## The three endpoints

| Endpoint | Purpose | Rate limit |
| - | - | - |
| `POST /api/v1/auth/forgot-password` | Starts the flow and emails the single-use reset link | 3 / min per IP, plus 10 / hour per email address |
| `POST /api/v1/auth/reset-password` | Consumes the reset link and sets the new password | 5 / min per IP |
| `GET /api/v1/auth/post-reset-revoke-api-keys` | The one-click link in the follow-up email; revokes the user's API keys | 10 / min per IP |

### 1. Request the reset email

```bash theme={null}
curl -s -X POST https://api.orbit.devotel.io/api/v1/auth/forgot-password \
  -H 'Content-Type: application/json' \
  -d '{ "email": "ada@example.com" }'
```

The response is `{ "success": true }` **whether or not the address belongs
to an account**. The endpoint never reveals which addresses are
registered, so a missing email is not an error you can detect from the
response: if nothing arrives, first confirm the address is provisioned in
your organization, then check spam (see [Failure modes](#failure-modes)).

The link in the email is valid for **1 hour** and works **once**.

### 2. Set the new password

```bash theme={null}
curl -s -X POST https://api.orbit.devotel.io/api/v1/auth/reset-password \
  -H 'Content-Type: application/json' \
  -d '{ "token": "TOKEN_FROM_THE_EMAIL_LINK", "password": "a long never-used-before password" }'
```

On success:

* The password changes and **every other active session for the account is
  signed out.** The browser that performed the reset stays signed in; all
  other devices must sign in again with the new password.
* The link is consumed. A second use returns `410`.
* A follow-up email arrives, "Your Orbit password was reset — review API
  keys", carrying the revocation link described next.

A rejected password (`422`: too weak, or seen in known breach corpora)
does **not** consume the link. Retry the same link with a stronger
password; see [Troubleshooting: password rejected during password
reset](/troubleshooting/password-reset-policy-rejected).

### 3. Revoke the pre-reset API keys

The **Revoke all my API keys** button in the follow-up email opens:

```bash theme={null}
curl -s "https://api.orbit.devotel.io/api/v1/auth/post-reset-revoke-api-keys?token=REVOKE_TOKEN_FROM_THE_EMAIL_LINK"
```

No sign-in is required: possession of the link is the authority, so treat
it like a credential. Because it is clicked from an email client, the
answer is a small HTML confirmation page stating how many keys were
revoked, not JSON. The link lives for **24 hours** and works once; a
second click returns `410` and revokes nothing.

## Auth-token lifecycle

| Token | Lifetime | Uses | When expired or reused |
| - | - | - | - |
| Reset link | 1 hour | Once | `410`; request a fresh link |
| Revocation link | 24 hours | Once | `410`; revoke from the dashboard instead |

* **Single-use by design.** A successfully used link can never be
  replayed, even inside its lifetime.
* **The flow fails closed.** A link that cannot be validated (expired,
  used, or corrupted) is refused with the same `410`, and corrupt-token
  events raise an operator alert instead of logging quietly, so a damaged
  token is never silently mistaken for a routine expiry.
* **The revocation is idempotent.** Already-revoked keys stay revoked, and
  concurrent clicks of the same link cannot double- or half-revoke; a
  second click simply receives the `410`.

## What the revocation does and does not do

It **does**:

* Deactivate **every active API key the recovering user created**, in one
  step.
* Take effect immediately: keys are deactivated in storage and pushed out
  of the platform's authentication cache, so live integrations stop
  authenticating with them right away rather than on a cache refresh.
* Report the exact count on the confirmation page.

It **does not**:

* Touch keys created by **other members** of your organization, or
  org-level keys that are not attributed to the recovering user.
* Touch keys minted **after the link is used**. Plan the order: click the
  revocation link first, then mint replacements. A key created between the
  reset and the click is revoked along with the old set.
* Replace the dashboard key manager. You can revoke any key individually
  at any time from **Settings → API keys**; the link is the bulk action
  for the recovery moment.
* Restore sessions. The reset already signed every other session out.

<Warning>
  Integrations that authenticate with the user's keys start failing with
  `401` as soon as the link is used. Before the user clicks, confirm which
  integrations hold their keys and have replacement keys ready to mint.
</Warning>

## Deploying securely

* **Rate limits.** The tight 3 / min IP cap on `forgot-password`, layered
  with the 10 / hour per-address cap, keeps the email channel from being
  abused during recovery. A legitimate user who exhausts the hourly cap
  can wait it out or complete a reset, which clears the cap.
* **IP allowlisting.** After revoking, mint replacement keys with an [IP
  allowlist](/guides/api-key-ip-allowlist) so a stolen replacement cannot
  be used from anywhere else. [API keys end to
  end](/guides/api-keys-end-to-end) covers minting and rotation.
* **Alerting.** A password change notifies the account in-app and by
  email, so a reset the user did not request surfaces to the real owner
  immediately. On the operator side, watch the three audit actions listed
  [below](#what-the-operator-sees-in-the-audit-log); an
  `auth.password_reset_requested` without a matching completed reset is
  worth a look.
* **What to tell users before they click the revoke link.** It works
  once, lives 24 hours, and revokes every key they created, including any
  minted between the reset and the click. Every integration using those
  keys will need fresh credentials. If they did not reset their password,
  they should contact support immediately without clicking anything.

## Failure modes

| Symptom | Cause | What to do |
| - | - | - |
| `200` from `forgot-password` but no email | Address not provisioned, the per-address hourly cap was reached, or inbox filtering | Confirm the membership; check spam; a completed reset clears the hourly cap |
| `410` at `reset-password` | Link older than 1 hour, or already used | Request a fresh link |
| `422` at `reset-password` | New password failed the complexity policy or appears in breach corpora | The link is still valid; retry with a stronger password |
| `410` at `post-reset-revoke-api-keys` | Link older than 24 hours, or already used | No keys were revoked; revoke them from **Settings → API keys** |
| `429` on any of the three | Rate limit exceeded | Honor `details.retry_after` in the response |

Both emails carry a plain-text copy of their link under the button, so a
client that strips HTML buttons still shows a clickable URL. When neither
the button nor the text copy arrives, the usual cause is a corporate
filter quarantining a first-time sender; have the user allowlist the
sender before requesting another link.

## What the operator sees in the audit log

Each step writes to your organization's [audit log](/guides/audit-log)
(**Settings → Audit log**):

| Action | Written when | Resource |
| - | - | - |
| `auth.password_reset_requested` | The reset email is actually dispatched; never for unknown addresses | `user` |
| `auth.password_reset_completed` | The password change succeeds | `user` |
| `auth.post_reset_api_keys_revoked` | The revocation link is used; `details.revoked_count` says how many | `api_key` |

A revocation entry looks like this:

```json theme={null}
{
  "timestamp": "2026-10-16T09:41:22Z",
  "user": "ada@example.com",
  "action": "auth.post_reset_api_keys_revoked",
  "resource": "api_key",
  "details": { "revoked_count": 3 },
  "ip_address": "203.0.113.7"
}
```

Filter on `action=auth.` to pull the whole recovery sequence for one
user, and export the window around an incident as CSV or JSON for your
evidence binder.

## The emails the user receives

**Reset email** — subject "Reset your Orbit password": a greeting by
name, a **Reset Password** button, the statement that the link expires in
1 hour, the plain-text copy of the link, and the standard "if you didn't
request this, ignore this email" note.

**Follow-up email** — subject "Your Orbit password was reset — review API
keys": a **Revoke all my API keys** button, the note that the link expires
in 24 hours and works once, and the instruction to contact support
immediately if the password change was not theirs.

## See also

* [Authentication](/authentication) — sessions, API key formats, and
  rotation.
* [Troubleshooting: ACCOUNT\_LOCKED at dashboard
  sign-in](/troubleshooting/account-locked-login) — when the lock, not the
  password, is the problem.
* [Rate limits](/guides/rate-limits) — the platform-wide rate-limit
  surface.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.