curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/prepare" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1",
"acceptance_payload": "0x<new-operator-acceptance-payload>",
"attestor": "0x<directory-attestor-account>",
"attestation_signature": "0x<directory-attestation-signature>",
"new_operator_signature": "<base64-ed25519-signature>"
}'
Personas
Prepare a Persona transfer
Bind the new operator acceptance and prepare the current controller authorization payload
curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/prepare" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1",
"acceptance_payload": "0x<new-operator-acceptance-payload>",
"attestor": "0x<directory-attestor-account>",
"attestation_signature": "0x<directory-attestation-signature>",
"new_operator_signature": "<base64-ed25519-signature>"
}'
Submit the verified transfer intent with the new operator’s acceptance signature. The Directory binds that signature and the DNS attestation into the outer
Decode
canonical_payload for an active current Persona controller to sign. The chain verifies the new-operator signature when the final transfer is submitted.
Repeat the Persona, operators, new controller set, constraints, challenge and operator nonces, new-operator signer, and verification_expires_at from verification. Those values are bound by acceptance_payload and a mismatch is rejected. The current controller may choose a fresh active signer_key_id and future valid_until for this outer authorization; repeat those exact outer values in the final transfer request.
Request
In addition to the complete body documented by Verify a Persona transfer challenge, include these fields:string
required
Exact value returned by transfer verification.
string
required
Directory attestor account returned by verification.
string
required
Directory DNS-attestation signature returned by verification.
string
required
Signature by
new_operator_signer_key_id over the decoded acceptance_payload bytes.curl --request POST \
--url "https://directory.example.com/personas/example.com/transfer/dns-challenges/<challenge-id>/prepare" \
--header "Content-Type: application/json" \
--data '{
"action": "transfer_persona",
"persona": "example.com",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"new_controller_keys": [
"did:openpayload:2222222222222222222222#keys-1"
],
"new_controller_threshold": 1,
"delivery_constraints": {
"requested_cache_seconds": 86400,
"max_http_envelope_bytes": 26214400,
"max_unchunked_message_bytes": 16777216,
"max_chunk_bytes": 2097152,
"max_replicas": 2
},
"challenge_id": "<challenge-id>",
"verification_expires_at": 1796083200000,
"new_operator_signer_key_id": "did:openpayload:2222222222222222222222#keys-1",
"nonce": "<current-operator-nonce>",
"valid_until": "<epoch-milliseconds>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1",
"acceptance_payload": "0x<new-operator-acceptance-payload>",
"attestor": "0x<directory-attestor-account>",
"attestation_signature": "0x<directory-attestation-signature>",
"new_operator_signature": "<base64-ed25519-signature>"
}'
Response
200 Response
{
"challenge_id": "<challenge-id>",
"persona": "example.com",
"action": "transfer_persona",
"challenge_nonce": "3",
"operator_nonce": "7",
"record_name": "_openpayload-persona.example.com",
"record_type": "TXT",
"record_value": "openpayload-persona-v1=<challenge>",
"expires_at": "2026-09-17T18:00:00Z",
"current_operator_did": "did:openpayload:1111111111111111111111",
"new_operator_did": "did:openpayload:2222222222222222222222",
"verification_expires_at": 1796083200000,
"status": "prepared",
"canonical_payload": "0x<current-controller-authorization-payload>",
"signer_key_id": "did:openpayload:1111111111111111111111#keys-1"
}
canonical_payload and sign those bytes with the returned current signer_key_id. The resulting signature is the signature field for the final transfer request.
Straight to the point
POST /personas/{persona}/transfer/dns-challenges/{challenge_id}/prepare
- New operator signs
acceptance_payload - Current Persona controller signs the returned
canonical_payload - Preparing does not consume the challenge or submit the transfer

