Authentication-Based Recovery API (Email / SMS)
Use this API to register an email address or phone number as a recovery method, then obtain a service-managed guardian's signature after the user verifies a one-time password (OTP).
The signature counts as one guardian approval under the account's recovery threshold. Submit it through the Recovery UX API to continue recovery.
Before You Start
- Deploy the Safe account and enable the Social Recovery Module.
- Request service access to obtain your service URL and Bearer token.
- Register the recovery method while the user still controls the account. Registrations are specific to an account and chain.
For a TypeScript walkthrough, see Enable Email/SMS Recovery and the Email/SMS Recovery Flow.
Authentication
Include Authorization: Bearer YOUR_BEARER_TOKEN in every request. Replace https://yourcompany.recovery.candide.dev with your service URL.
Registration, registration lookup, and deletion also require a SIWE message signed by the Safe account. During recovery, the user verifies codes sent to registered channels to authorize the service-managed guardian's signature. See SIWE signing.
Examples use placeholder addresses, IDs, messages, and signatures. Replace them with values from your integration. Expressions such as siwe(...) and sign(message) describe values to generate in your application; they are not literal API inputs.
Integration Workflow
Set up recovery while the user has account access:
- Call
/auth/registerwith a Safe-signed SIWE message to send a verification code. - Submit the code to
/auth/submitto receive aregistrationIdandguardianAddress. - Add that guardian address to the account's Social Recovery Module on-chain. Registration alone does not add the guardian.
Recover the account after access is lost:
- Call
/auth/signature/requestwith the proposed new owners and their signing threshold. - Submit verification codes to
/auth/signature/submituntil the response includessignerandsignature. - Use that signature to create a recovery request, or add it to an existing request with the same recovery data. Collect any other required guardian approvals, execute recovery, and finalize after the grace period.
There are three separate thresholds:
| Value | What it controls |
|---|---|
requiredVerifications | How many channel verifications are needed to obtain the service-managed guardian's signature. |
| Guardian threshold | How many guardian approvals the Social Recovery Module requires to execute recovery. |
newThreshold | How many of the new Safe owners must sign account transactions after recovery. |
Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /auth/register | Request registration and send a verification code. |
| POST | /auth/submit | Verify the code and return the guardian address. |
| GET | /auth/registrations | List the account's registered methods on a chain. |
| POST | /auth/delete | Delete a registered method. |
| POST | /auth/signature/request | Request verification codes for a proposed recovery. |
| POST | /auth/signature/submit | Verify a code and return a guardian signature once enough verifications are collected. |
Register and Manage Recovery Methods
Register an Email Address or Phone Number
Send a verification code to the selected email address or phone number. Save the returned challengeId for /auth/submit.
POST /auth/register
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/auth/register \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"account":"0x...",
"chainId": 1,
"channel":"email",
"target":"user@example.com",
"message": "siwe(chainId, statement(channel, target))",
"signature": "sign(message)"
}'
{
"challengeId": "unique-challenge-id"
}
| key | type | description |
|---|---|---|
account | string | The smart account address requesting registration |
chainId | number | The chain ID where the account resides |
channel | string | Authentication channel: 'email' or 'sms' |
target | string | The email or phone number for authentication |
message | string | SIWE (EIP-4361) message signed by the account |
signature | string | Signature proving the request is initiated from the account |
| key | type | description |
|---|---|---|
challengeId | string | Unique challenge ID for the registration, used in /auth/submit |
account: The smart account address requesting registration.chainId: The chain where the Safe is deployed. Register separately for each chain.channel: Either"email"or"sms"(defines the authentication type).target: The email or phone number for authentication.signature: A signature valid for the Safe account over the complete SIWE message.message: The complete SIWE message containing this exact statement:
I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} (via {{channel}})
See SIWE signing for the exact statement and signing requirements.
Verify Registration
Submit the code to confirm ownership of the email address or phone number. The response returns the guardian address to add to the Social Recovery Module on-chain.
POST /auth/submit
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/auth/submit \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"challengeId":"unique-challenge-id",
"challenge":"123456"
}'
{
"registrationId": "unique-registration-id",
"guardianAddress": "0x..."
}
| key | type | description |
|---|---|---|
challengeId | string | The unique challenge ID received from /auth/register |
challenge | string | The OTP code received via email/SMS |
| key | type | description |
|---|---|---|
registrationId | string | Unique registration ID for the authenticated channel |
guardianAddress | string | The service-managed guardian address to add to the Social Recovery Module on-chain |
List Registered Recovery Methods
List the registered recovery methods for the specified Safe account and chain. The response contains an empty array if no methods are registered.
GET /auth/registrations
- Example Request
- Example Response
- Query Parameters
- Response Body
curl -G "https://yourcompany.recovery.candide.dev/auth/registrations" \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
--data-urlencode "account=0x..." \
--data-urlencode "chainId=1" \
--data-urlencode "message=siwe(chainId, statement)" \
--data-urlencode "signature=sign(message)"
{
"registrations": [
{
"id": "unique-registration-id",
"channel": "email",
"target": "user@example.com"
}
]
}
| key | type | description |
|---|---|---|
account | string | The Safe account address |
chainId | number | The chain ID where the account resides |
message | string | SIWE message signed by the account |
signature | string | Signature proving the request |
| key | type | description |
|---|---|---|
registrations | array | List of active registrations |
See SIWE signing for the exact statement and signing requirements.
Delete a Recovery Method
Delete the registration identified by registrationId, using a SIWE message signed by the Safe account. This removes the service registration; it does not revoke the guardian on-chain.
POST /auth/delete
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/auth/delete \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"registrationId":"unique-registration-id",
"message": "siwe(chainId, statement(registrationId))",
"signature": "sign(message)"
}'
{
"success": true
}
| key | type | description |
|---|---|---|
registrationId | string | The registration ID to delete |
message | string | SIWE message signed by the account |
signature | string | Signature proving the request |
| key | type | description |
|---|---|---|
success | boolean | True if deletion was successful |
See SIWE signing for the exact statement and signing requirements.
Obtain a Recovery Signature
Request Recovery Verification
Propose new Safe owners and their signing threshold. The service sends verification codes to registered channels and returns a requestId, the required number of verifications, and the available challenges.
POST /auth/signature/request
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/auth/signature/request \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"account":"0x...",
"newOwners": ["0x...", "0x..."],
"newThreshold": 2,
"chainId": 1
}'
{
"requestId": "unique-signature-request-id",
"requiredVerifications": 1,
"auths": [
{
"challengeId": "unique-challenge-id",
"channel": "email",
"target": "us**@exa****.com"
}
]
}
| key | type | description |
|---|---|---|
account | string | The smart account address to be recovered |
newOwners | string[] | The new owners for the Safe account |
newThreshold | number | The signing threshold for the new Safe owners after recovery; not the guardian approval threshold |
chainId | number | Chain ID for the recovery request |
| key | type | description |
|---|---|---|
requestId | string | Unique signature request ID |
requiredVerifications | number | Minimum number of OTP challenges required |
auths | array | List of authentication methods to verify |
Verify a Code and Collect the Signature
Submit a code with its challengeId and the shared requestId. Each successful submission verifies one challenge. The response includes signer and signature only after requiredVerifications is met.
POST /auth/signature/submit
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/auth/signature/submit \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"requestId": "unique-signature-request-id",
"challengeId": "unique-challenge-id",
"challenge": "123456"
}'
{
"success": true,
"signer": "0x...",
"signature": "0x..."
}
| key | type | description |
|---|---|---|
requestId | string | The unique ID from /auth/signature/request |
challengeId | string | The challenge ID specific to the authentication method |
challenge | string | The OTP code received via email/SMS |
| key | type | description |
|---|---|---|
success | boolean | True if verification was successful |
signer | string | Guardian address (available only if sufficient verifications collected) |
signature | string | Recovery signature (available only if sufficient verifications collected) |
If more verifications are needed, a successful submission returns only:
{
"success": true
}
Continue with another challenge from the same request until signer and signature are returned. A guardian signature authorizes recovery; it does not execute a transaction or change ownership by itself.
How to Sign Messages (SIWE EIP-4361)
SIWE (Sign-In with Ethereum) messages authorize registration and alert-management actions. Submit the complete prepared message in message and its corresponding signature in signature.
Choose the Signing Account
| Endpoints | SIWE address and signature |
|---|---|
/auth/register, /auth/registrations, /auth/delete | Use the Safe account address as the SIWE address. Produce a Safe-compatible signature that satisfies the account's owner threshold. |
/alerts/subscribe, /alerts/subscriptions, /alerts/unsubscribe | Use the owner address as the SIWE address. Sign with that owner. Subscription creation checks that this address is an owner of the Safe. |
For Safe accounts, follow the registration signing example, which uses the Safe message-signing helpers and formats the owner signatures for verification. For alert subscriptions, follow the Recovery Alerts Guide.
Use the Exact Statement
The service checks the statement against the requested action. Preserve its wording and punctuation, and substitute the values in braces.
| Action | Statement |
|---|---|
| Register a recovery method | I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} (via {{channel}}) |
| List recovery methods | I request to retrieve all authentication methods currently registered to my account with Safe Recovery Service |
| Delete a recovery method | I request to remove the authentication method with registration ID {{id}} from my account on Safe Recovery Service |
Use the registration ID for {{id}}, the email address or phone number for {{target}}, and email or sms for {{channel}}. The alert endpoints list their own statements.
Prepare and Sign the Message
- Construct a SIWE message with the signing address above, the correct chain ID, the exact statement, a domain and URI, a fresh nonce, and an issued-at timestamp.
- Prepare the message with a SIWE library or the Recovery Service SDK.
- Sign that complete message using the appropriate signing account and submit both values unchanged.
The statement alone is not the full SIWE message. The chain ID must match the request, or the registration's chain for deletion. Generate a fresh message when retrying an expired authorization.
Error Handling
Use the HTTP status to determine whether a request succeeded. Service errors contain top-level code and message fields. For example:
{
"code": 404,
"message": "Could not find registration with this id"
}
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid parameters or missing required fields |
| 401 | Unauthorized - Invalid or missing Bearer token |
| 403 | Forbidden - Signature verification failed, challenge invalid or expired, or action not permitted |
| 404 | Not Found - Resource not found |
| 409 | Conflict - The recovery method is already registered |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error - Something went wrong on the server |
Troubleshooting
- Invalid signature: Check the signing address, chain ID, and exact message or recovery data. SIWE messages must use the statement for the requested action.
- Invalid or expired code: Verify that the code matches the challenge ID. If it has expired, start a new registration or recovery verification request as appropriate.
- Missing resource: Use the ID returned by the corresponding creation endpoint.
- Rate limit: Wait before retrying the request.