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

# Recover a domain Persona

> Replace a lost or previous operator DID using domain proofs and an elapsed recovery window

Recovery lets the current domain administrator replace a Persona operator when the old key is lost or the old operator refuses to transfer it. It works for an existing domain Persona, including an expired or revoked one. Single-label Personas cannot use this procedure.

<Note>
  Recovery requires runtime spec 124 or later and the new Directory release. These changes are staged and have not yet been deployed to alpha. Email proof also requires the Directory operator to configure delivery to the fixed recovery mailbox.
</Note>

Private keys remain local. The Directory verifies DNS and email, prepares the canonical SCALE payload, and submits the locally signed request. The chain independently checks attestor admission, proof signatures, replacement-DID signatures, proof expiry, and recovery timing.

## Recovery conditions

| Evidence | Completion |
| - | - |
| Fresh DNS proof and fresh email proof | Immediate, including after controller cancellation |
| Fresh DNS or email proof, plus seven elapsed days | Permitted, including after cancellation |
| Neither proof | Never |

The seven-day window begins when the first verified proof is included in finalized chain state. A controller cancellation records an objection and cannot restart the window or prevent an eligible recovery. Time alone never establishes authority.

DNS and email proof challenges expire after 48 hours. Refresh proof near day seven for a recovery using one proof. A pending chain request expires after 14 days; refreshing proof does not restart its original window. Pending requests are bounded per domain, and expired chain entries are pruned when a new request is submitted. The Directory periodically purges expired local challenges.

DNS and email both depend on domain administration. Changing DNS MX records can redirect recovery email, so these are not independent factors against compromise of DNS administration.

## 1. Create the challenge

The replacement DID must already be active and publish its Ed25519 signing keys. Request initial controllers and choose what happens to the policy:

```json theme={null}
{
  "operator_did": "did:openpayload:2222222222222222222222",
  "controller_keys": ["did:openpayload:2222222222222222222222#root"],
  "policy": "preserve"
}
```

Send that body to:

```http theme={null}
POST /personas/{persona}/recovery/challenges
```

The response includes a `recovery_id` as `0x` followed by 64 hexadecimal characters, the exact DNS `record_name` and `record_value`, `expires_at` in Unix milliseconds, and the fixed recovery email address. It may include the issuing `directory_url`; keep using that Directory for challenge verification and submission.

Publish the exact TXT value at the returned record name. Up to eight distinct Ed25519 controller keys are permitted; the threshold remains `1`.

### Preserve or clear the policy

`policy` defaults to `preserve`. Recovery retains graph rules, references, policy IDs, and revisions, and assigns their operator metadata to the replacement DID. References to the old DID inside graphs are not rewritten. The preserved policy is the policy stored when recovery executes.

`policy: "clear"` atomically removes the Persona's V2 and V3 policies while replacing the operator. Persona delivery constraints remain unchanged in both modes. The choice and new controllers are bound to the signed recovery request.

## 2. Optionally verify email

Request delivery only to `openpayload-recovery@{persona}`:

```http theme={null}
POST /personas/{persona}/recovery/{recovery_id}/email
```

The address cannot be overridden. The response reports delivery acceptance and expiry; it never contains the confirmation token. Confirm the token supplied by the email:

```http theme={null}
POST /personas/{persona}/recovery/{recovery_id}/email/confirm
```

```json theme={null}
{"token":"<token-delivered-by-email>"}
```

Tokens are single-use and expire with the 48-hour challenge. Failed confirmation attempts are limited. Email delivery can be unavailable while DNS-only recovery remains available.

## 3. Prepare, sign, and submit

Send an unsigned intent:

```json theme={null}
{
  "action": "start_recovery",
  "operator_did": "did:openpayload:2222222222222222222222",
  "signer_key_id": "did:openpayload:2222222222222222222222#root",
  "verification_days": 365,
  "use_dns": true
}
```

```http theme={null}
POST /personas/{persona}/recovery/{recovery_id}/prepare
```

The Directory derives verified proof factors; clients cannot claim unverified factors. `use_dns: false` permits an email-only request. `verification_days` accepts 1–365 and defaults to 365; `verification_expires_at` can supply an explicit absolute expiry instead. The Directory fills in the current operator nonce and a short authorization expiration when omitted.

The response contains `canonical_payload` and the complete normalized `request`. Decode the hexadecimal payload and sign its exact bytes locally. Preserve the returned request fields, add `canonical_payload` and a Base64 or hexadecimal `signature`, then submit:

```http theme={null}
PUT /personas/{persona}/recovery/{recovery_id}
```

`202 Accepted` returns `tx_id`; it means submission for processing. Wait for finalized recovery state. Invalid signatures, unverified factors, stale nonces, modified canonical payloads, or expired challenges are rejected before submission.

## 4. Check, refresh, or finalize

```http theme={null}
GET /personas/{persona}/recovery/{recovery_id}
```

A finalized pending record includes its replacement DID, controllers, policy choice, start and expiration times, proof expirations, and `cancelled`. The response supplies `ready_at`, `eligible`, and `proof_refresh_required`. A local challenge without a chain request reports `awaiting_proof`.

To refresh an existing pending request, create a challenge with the same replacement DID, controllers, and policy choice:

```http theme={null}
POST /personas/{persona}/recovery/{recovery_id}/challenges
```

Publish or confirm the new value, prepare action `prove_recovery`, sign, and submit through the same prepare and submission routes. This refreshes proof without changing `started_at`.

Prepare action `finalize_recovery` with the replacement DID and a recovery-controller signing key when `eligible` is true. Sign and submit it through the same routes. The finalized Persona becomes active under the new operator, and pending recovery requests for that Persona are removed.

## Controller cancellation

A current Persona controller can prepare action `cancel_recovery`, using the current operator DID and its authorized signing key. Sign and submit through the same routes. Cancellation records an objection; an eligible recovery follows the conditions above.

Recovery replaces Persona ownership. It does not restore a DID private key or transfer separately registered Applications.

## CLI

The OpenPayload `openpayload_register.py` tool supports registration, renewal, DNS hooks for scheduled jobs, saved progress, and recovery. Use `persona recover --policy preserve` or `--policy clear`; the default is `preserve`. See the [tool instructions](https://github.com/Mandolare-LLC/OpenPayload/blob/main/tools/README.md).

## Straight to the point

```text theme={null}
Create: POST /personas/{persona}/recovery/challenges
Refresh challenge: POST /personas/{persona}/recovery/{recovery_id}/challenges
Request email: POST /personas/{persona}/recovery/{recovery_id}/email
Confirm email: POST /personas/{persona}/recovery/{recovery_id}/email/confirm
Prepare: POST /personas/{persona}/recovery/{recovery_id}/prepare
Submit: PUT /personas/{persona}/recovery/{recovery_id}
Status: GET /personas/{persona}/recovery/{recovery_id}
```


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