# Recovery Step-Up

The owner's passkey confirmation of a signature with a wallet's backup share: a token for that one signature, for two minutes.

## Overview

- **When.** A signature whose committee holds `BACKUP` — the user lost their device and signs with the backup and Aurea's cosigner ([Start Signing Ceremony](https://docs.aureahub.com/docs/mpc-sign-start.md) › Committees) — always needs the owner's step-up, for every tenant: without it `403` `STEP_UP_REQUIRED`, with a wrong one `403` `STEP_UP_INVALID`, and no ceremony starts.
- **Two ways.** The owner's current password, sent as `password` with the signature — the End User's way, and any role's —; or, for a role that holds passkeys, a passkey step-up token sent as `stepUpToken`, made with the two routes below.
- **Bound to one wallet and one action**: `mpc_wallet.recovery_sign` on that wallet, for the wallet's own user (`403` `MPC_WALLET_NOT_OWNED`, `404` `MPC_WALLET_NOT_FOUND`). These are the MPC wallets' own step-up routes: `POST /v1/auth/step-up/options` does not take this action.
- **The passkey** is one of the user's active passkeys, on Aurea's relying party or your tenant's own passkey address (`rpId`) — [Passkeys & Step-Up](https://docs.aureahub.com/docs/guide-passkeys.md). None there: `409` `STEP_UP_NO_PASSKEY` — use the password.

## With the Signature

1. Ask the options for the wallet (Options), and have the browser answer them with `navigator.credentials.get()`, the user's PIN or biometric.
2. Send the answer to `POST /v1/mpc/step-up`: `stepUpToken` (The Token).
3. Within two minutes, start the signature with it: [Start Signing Ceremony](https://docs.aureahub.com/docs/mpc-sign-start.md) with `committee` `["BACKUP","AUREA"]` and `stepUpToken`. The token is used once: another signature from the backup needs a new one.

```javascript
// A signature with the backup share, confirmed by the owner's passkey
const options = await post(`/v1/mpc/wallets/${walletId}/step-up/options`, { action: 'mpc_wallet.recovery_sign' });
const credential = await navigator.credentials.get({
  publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options.publicKey),
});
const { stepUpToken } = await post('/v1/mpc/step-up', { ceremonyId: options.ceremonyId, credential: credential.toJSON() });
const { sessionId } = await post(`/v1/mpc/wallets/${walletId}/sign/start`, {
  committee: ['BACKUP', 'AUREA'], msgHashHex, wireVersion: 2, stepUpToken,
});
// Without a passkey: send { committee: ['BACKUP', 'AUREA'], msgHashHex, wireVersion: 2, password } instead.
```

## Options

### `POST /v1/mpc/wallets/{walletId}/step-up/options`

Authentication: bearer token required.

The passkey step-up's options for an action on the caller's MPC wallet: answer them at POST /v1/mpc/step-up.

**Path parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `walletId` | string (uuid) | yes | The caller's MPC wallet |

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `action` | string | yes | `mpc_wallet.recovery_sign`: a signature with the backup share |
| `rpId` | string | no | Your tenant's passkey address, for a step-up in your own app; absent, Aurea's own |

**Responses**

`200` OK

```json
{
  "ceremonyId": "5e9a2c71-3b8d-4f06-a4e2-7d1c9b0f3a58",
  "action": "mpc_wallet.recovery_sign",
  "expiresAt": "2026-11-10T09:25:00.000Z",
  "publicKey": {
    "rpId": "access.aureahub.com",
    "challenge": "-5LR9MtkIpGMrKdJwNwCf7R8JvV3a3JVP243INWKB90",
    "allowCredentials": [{ "id": "AkuuQ3HZmehDc7su8X9U7g6kRKc", "type": "public-key" }],
    "userVerification": "required"
  }
}
```

`409` No Passkey

```json
{
  "statusCode": 409,
  "error": "ConflictError",
  "message": "…",
  "details": { "code": "STEP_UP_NO_PASSKEY" }
}
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/mpc/wallets/7c1e9b24-3f5a-4d8e-9b6c-1a2f3e4d5c6b/step-up/options \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "action": "mpc_wallet.recovery_sign" }'
```

- Used once, within 5 minutes; `Cache-Control: no-store`. `400` when the wallet id or the body is not valid; `403` for a role that cannot use MPC wallets or another user's wallet (`MPC_WALLET_NOT_OWNED`); `404` `MPC_WALLET_NOT_FOUND`.

## The Token

### `POST /v1/mpc/step-up`

Authentication: bearer token required.

Verifies the passkey's answer to the MPC step-up's options and answers a single-use step-up token for that action on that wallet.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ceremonyId` | string (uuid) | yes | `ceremonyId` of the options |
| `credential` | object | yes | The assertion's `toJSON()`: AuthenticationResponseJSON |

**Responses**

`200` OK

```json
{
  "stepUpToken": "EXAMPLE-step-up-token-000000000000000000002",
  "action": "mpc_wallet.recovery_sign",
  "expiresAt": "2026-11-10T09:22:41.000Z"
}
```

`403` Did not verify

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "…",
  "details": { "code": "STEP_UP_INVALID" }
}
```

**Example request**

```bash
curl -X POST https://api.aureahub.com/v1/mpc/step-up \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "ceremonyId": "5e9a2c71-3b8d-4f06-a4e2-7d1c9b0f3a58", "credential": { "id": "AkuuQ3HZmehDc7su8X9U7g6kRKc", "type": "public-key", "response": { "…": "…" } } }'
```

- The token lives 2 minutes and is used once, by the wallet's user, for that action on that wallet. Keep it in memory: `Cache-Control: no-store`.
- `403` `STEP_UP_INVALID` for anything wrong — options unknown, used, expired or another user's, a passkey that is not one of the user's active ones, an answer that does not verify. Ask for new options.

---

Web version: https://docs.aureahub.com/#mpc-step-up
