Safe Recovery UX API
Benefits
| Feature | Description |
|---|---|
| Alerts and Notifications | Account owners subscribe to receive notifications via email or SMS when a recovery request is initiated on-chain. |
| Guardians Sign Once | Off-chain signature collection eliminating the need for guardians to share links with one another. |
| Privacy Guaranteed | Guardians sign only off-chain and do not need to maintain a balance in their accounts, allowing them to preserve their pseudonymity with fresh accounts. |
| Social Engineering Protection | A communication system using emojis that allows guardians to verify and approve legitimate recovery requests from their rightful owners. |
| Auto Finalization After Grace Period | A built-in relayer automatically submits signed transactions on behalf of guardians for confirmation and finalization once the grace period has elapsed. |
To get started, request access here.
Authentication
All API requests require a Bearer token in the Authorization header:
curl -X POST \
https://yourcompany.recovery.candide.dev/recoveries/create \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-d '{...}'
/recoveries
- /recoveries/create
- POST: Creates a new recovery request
- /recoveries/fetchByAddress
- GET: Fetches recovery requests
- /recoveries/listByAddress
- GET: Lists all recovery requests with filtering and pagination
- /recoveries/fetchById
- GET: Fetch a recovery request by ID
- /recoveries/sign
- POST: Collects a guardian signature
- /recoveries/execute
- POST: Execute a recovery request by ID
- /recoveries/finalize
- POST: Finalize a recovery request by ID
/alerts
- /alerts/subscribe
- POST: Creates an inactive alerts subscription for an account
- /alerts/activate
- POST: Activate subscription to recovery requests
- /alerts/subscriptions
- GET: Fetches active alerts subscriptions for an account.
- /alerts/unsubscribe
- POST: Unsubscribes from an active alerts subscription.
Recoveries
Create Recovery Request
Creates a new recovery request by a guardian with a lost signer of a Safe account. Can only be initiated by guardians of the account.
POST /recoveries/create
- Example Request
- Example Response
- Request Body
- Response Body
curl -X POST \
https://yourcompany.recovery.candide.dev/create \
-H 'Content-Type: application/json' \
-d '{
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"newOwners": ["0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D"],
"newThreshold": 2,
"chainId": 1,
"signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
{
"id": "123456789",
"emoji": "🤖😅🥵👻🖖",
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"chainId": 1,
"newOwners": [
"0x41153290c995c8c4410d50f95D87ee86A1B07eeC",
"0xB97A1C3993A551f0Febf030539630ACb77E6832D"
],
"newThreshold": 2,
"nonce": "1234567890",
"signatures": [],
"executeData": {
"sponsored": true,
"transactionHash": ""
},
"finalizeData": {
"sponsored": true,
"transactionHash": ""
},
"status": "PENDING",
"discoverable": true,
"createdAt": "2023-04-18T12:34:56.789Z",
"updatedAt": "2023-04-18T12:34:56.789Z"
}
| key | type | description |
|---|---|---|
account | string | The Safe account address that the owner wants to recover. Must be a valid Ethereum address |
newOwners | string[] | The new owners to the Safe account. Must be a valid Ethereum addresses |
newThreshold | number | The new threshold to the Safe account |
chainId | number | The chainId of the network where the Safe account resides |
signer | string | The guardian public address initiating the recovery request |
signature | string | A signature from the guardian for a message containing the address of the safe, new owners of the Safe, chainId, module address, and a nonce |
| key | type | description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | The ID associated with the Recovery Request | |||||||||
emoji | string | The emojis associated with the recovery request | |||||||||
account | string | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | |||||||||
chainId | number | The chainId of the network where the Safe account resides | |||||||||
newOwners | string[] | The new owners for the Safe account | |||||||||
newThreshold | number | The new threshold for the Safe account | |||||||||
nonce | bigint | Recovery module contract nonce | |||||||||
signatures | json | The signatures for the recovery request | |||||||||
executeData | | An object field representing the finalization recovery transaction | |||||||||
finalizeData | | An object field representing the finalization recovery transaction | |||||||||
status | string | The status of the recovery request: PENDING | EXECUTED | FINALIZED | FINALIZATION-IN-PROGRESS | FAILED | |||||||||
discoverable | boolean | Whether the recovery request is discoverable | |||||||||
createdAt | datetime | The date the recovery request was created | |||||||||
updatedAt | datetime | The date and time of the recovery request that was created |
Fetch recovery by address
Fetches a recovery request by Safe account address and nonce. Requires account, chainId, and nonce parameters.
GET /recoveries/fetchByAddress
- Example Request
- Example Response
- Request Body
- Response
curl -G "https://yourcompany.recovery.candide.dev/fetchByAddress" \
--data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \
--data-urlencode "chainId=11155111" \
--data-urlencode "nonce=0x1"
[
{
"id": "123456789",
"emoji": "🤖😅🥵👻🖖",
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"chainId": 1,
"newOwners": [
"0x41153290c995c8c4410d50f95D87ee86A1B07eeC",
"0xB97A1C3993A551f0Febf030539630ACb77E6832D"
],
"newThreshold": 2,
"nonce": "1234567890",
"signatures": [],
"executeData": {
"sponsored": true,
"transactionHash": ""
},
"finalizeData": {
"sponsored": true,
"transactionHash": ""
},
"status": "PENDING",
"discoverable": true,
"createdAt": "2023-04-18T12:34:56.789Z",
"updatedAt": "2023-04-18T12:34:56.789Z"
},
{
"id": "72682373",
"emoji": "💳📿🪣📥📹",
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"chainId": 1,
"newOwners": [
"0x73f7b1184B5cD361cC0f7654998953E2a251dd58",
"0x7Cb027917b27BCb5963C548657a008BF45b25BDc"
],
"newThreshold": 2,
"nonce": "1234567890",
"signatures": [],
"executeData": {
"sponsored": true,
"transactionHash": ""
},
"finalizeData": {
"sponsored": true,
"transactionHash": ""
},
"status": "PENDING",
"discoverable": true,
"createdAt": "2023-04-18T12:34:56.789Z",
"updatedAt": "2023-04-18T12:34:56.789Z"
}
]
| key | type | description |
|---|---|---|
account | string | The Safe account address that the owner wants to recover. Must be a valid Ethereum address |
chainId | number | The chainId of the network where the Safe account resides |
nonce | number | Recovery module contract nonce |
| key | type | description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
RecoveryRequest[] | | A list of ongoing Recovery Requests for a Safe account address |
List all recoveries by address
Lists all recovery requests for a Safe account with advanced filtering, pagination, and ordering. The executed and finalized filters are cross-checked with indexed on-chain data for accuracy.
GET /recoveries/listByAddress
- Example Request
- Example Response
- Query Parameters
- Response
curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \
--data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \
--data-urlencode "chainId=11155111" \
{
"recoveries": [
{
"id": "123456789",
"emoji": "🤖😅🥵👻🖖",
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"chainId": 1,
"newOwners": [
"0x41153290c995c8c4410d50f95D87ee86A1B07eeC",
"0xB97A1C3993A551f0Febf030539630ACb77E6832D"
],
"newThreshold": 2,
"nonce": "1234567890",
"signatures": [],
"executeData": {
"sponsored": true,
"transactionHash": ""
},
"finalizeData": {
"sponsored": true,
"transactionHash": ""
},
"status": "PENDING",
"discoverable": true,
"createdAt": "2023-04-18T12:34:56.789Z",
"updatedAt": "2023-04-18T12:34:56.789Z"
}
],
"total": 1
}
| key | type | description |
|---|---|---|
account | string | The Safe account address. Must be a valid Ethereum address |
chainId | number | The chainId of the network where the Safe account resides |
status | string | (Optional) Filter by status: PENDING, EXECUTED, FINALIZED, EXECUTION-IN-PROGRESS, FINALIZATION-IN-PROGRESS |
executed | boolean | (Optional) Filter by executed status. Cross-checked with indexed data |
finalized | boolean | (Optional) Filter by finalized status. Cross-checked with indexed data |
orderBy | string | (Optional) Field to order by (e.g., 'createdAt', 'nonce') |
order | string | (Optional) Order direction: 'asc' or 'desc'. Defaults to 'desc' |
| key | type | description | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
recoveries | | A list of Recovery Requests matching the filters | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
total | number | Total number of recovery requests matching the filters |
/recoveries/fetchByAddress: Use when you know the exact nonce. Ideal for checking a specific pending request or looking up the current recovery state./recoveries/listByAddress: Use when you need to query multiple requests with filtering, pagination, or sorting. Better for dashboard UIs showing recovery history.
Fetch recovery by ID
Fetch a recovery request by ID
GET /recoveries/fetchById
- Example Request
- Example Response
- Request Body
- Response Body
curl -G "https://yourcompany.recovery.candide.dev/fetchById" \
--data-urlencode "id=0x123"
{
"id": "123456789",
"emoji": "🤖😅🥵👻🖖",
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"chainId": 1,
"newOwners": [
"0x41153290c995c8c4410d50f95D87ee86A1B07eeC",
"0xB97A1C3993A551f0Febf030539630ACb77E6832D"
],
"newThreshold": 2,
"nonce": "1234567890",
"signatures": [],
"executeData": {
"sponsored": true,
"transactionHash": "",
},
"finalizeData": {
"sponsored": true,
"transactionHash": "",
},
"status": "PENDING",
"discoverable": true,
"createdAt": "2023-04-18T12:34:56.789Z",
"updatedAt": "2023-04-18T12:34:56.789Z"
}
| key | type | description |
|---|---|---|
id | string | Recovery request ID |
| key | type | description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
id | string | The ID associated with the Recovery Request | |||||||||
emoji | string | The emojis associated with the recovery request | |||||||||
account | string | The Safe account address that the owner wants to recover. Must be a valid Ethereum address | |||||||||
chainId | number | The chainId of the network where the Safe account resides | |||||||||
newOwners | string[] | The new owners for the Safe account | |||||||||
newThreshold | number | The new threshold for the Safe account | |||||||||
nonce | bigint | Recovery module contract nonce | |||||||||
signatures | json | The signatures for the recovery request | |||||||||
executeData | | An object field representing the finalization recovery transaction | |||||||||
finalizeData | | An object field representing the finalization recovery transaction | |||||||||
status | string | The status of the recovery request: PENDING | EXECUTED | FINALIZED | FINALIZATION-IN-PROGRESS | FAILED | |||||||||
discoverable | boolean | Whether the recovery request is discoverable | |||||||||
createdAt | datetime | The date the recovery request was created | |||||||||
updatedAt | datetime | The date and time of the recovery request that was created |
Collect a guardian signature
Collects a guardian signature to store for later confirmation and finalization
POST /recoveries/sign
- Example Request
- Example Response
- Request Body
- Response
curl -X POST \
https://yourcompany.recovery.candide.dev/sign \
-H 'Content-Type: application/json' \
-d '{
"id": "123456789",
"signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'
{
"success": "true"
}
| key | type | description |
|---|---|---|
id | string | Recovery request ID |
signer | string | The guardian public address |
signature | string | The signature for the message hash of the guardian confirmation of recovery |
| key | type | description |
|---|---|---|
status | true | true = signature is valid |
Execute a recovery by ID
POST /recoveries/execute
Execute a recovery request by ID
- Example Request
- Example Response
- Request Body
- Response
curl -X POST \
https://yourcompany.recovery.candide.dev/execute \
-H 'Content-Type: application/json' \
-d '{
"id": 123456789
}'
{
"success": "true"
}
| key | type | description |
|---|---|---|
id | string | Recovery request ID |
| key | type | description |
|---|---|---|
success | true | Return true once finilized. Else returns error |
Finalize recovery by ID
POST /recoveries/finalize
Finalize a recovery request by ID
- Example Request
- Example Response
- Request Body
- Response
curl -X POST \
https://yourcompany.recovery.candide.dev/finalize \
-H 'Content-Type: application/json' \
-d '{
"id": 123456789
}'
{
"success": "true"
}
| key | type | description |
|---|---|---|
id | string | Recovery request ID |
| key | type | description |
|---|---|---|
success | true | Return true once finilized. Else returns error |
Alerts
Alerts: Subscribe to recovery requests
POST /alerts/subscribe
Creates an inactive alerts subscription for an account, both onchain and offchain. It then needs to be activated through challenge submission using POST /alerts/activate.
- Example Request
- Example Response
curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/subscribe \
-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)"
}'
{
"subscriptionId": "unique-subscription-id", // please note that this alerts subscription needs to be activated using the next endpoint
}
account: The smart account address requesting registration.chainId: The chain id in which the account resides (this is used to verify the signature field only, the alert will trigger for any action for this account across any chain)channel: Either"email"or"sms"(defines the delivery channel).target: The email or phone number for authentication.message: SIWE (EIP-4361) message statement. Statement:
I agree to receive Social Recovery Module alert notifications for my account address on all supported chains sent to {{target}}
signature: signature proving the request is initiated from the account
See example guide how to construct the message and signature using Sign in With Ethereum (SIWE)
Alerts: Activate subscription to recovery requests
POST /alerts/activate
Verifies submitted challenge and activates alerts subscription.
- Example Request
- Example Response
curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/activate \
-H 'Content-Type: application/json' \
-d '{
"subscriptionId": "unique-subscription-id",
"challenge": "123456"
}'
{
"success": true,
}
subscriptionId: The unique ID received in the subscription response.challenge: The code received via email/SMS.
Alerts: Get active subscription
Fetches active alerts subscriptions for an account.
GET /alerts/subscriptions
- Example Request
- Example Response
curl -G "https://yourcompany.recovery.candide.dev/alerts/subscriptions" \
--data-urlencode "account=0x...",
--data-urlencode "chainId=0x1",
--data-urlencode "message=siwe(chainId, statement)",
--data-urlencode "signature=sign(message)"
{
"subscriptions": [
{
"id": "unique-subscription-id",
"channel": "email",
"target": "user@example.com"
}
]
}
account: The smart account address.chainId: The chain id in which the account resides (this is used to verify the signature field only, the alerts are global for this account accross all supported chains that have alerts enabled)message: SIWE (EIP-4361) message statement. Statement:
I request to retrieve all Social Recovery Module alert subscriptions linked to my account
signature: signature proving the request is initiated from the account
See example guide how to construct the message and signature using Sign in With Ethereum (SIWE)
Alerts: Unsubscribe
Unsubscribes from an active alerts subscription.
POST /alerts/unsubscribe
- Example Request
- Example Response
curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/unsubscribe \
-H 'Content-Type: application/json' \
-d '{
"subscriptionId": "unique-subscription-id"
}'
{
"success": true
}
subscriptionId: The unique ID received in the subscription response.
Error Handling
The API uses standard HTTP status codes to indicate the success or failure of a request. Error responses include a JSON object with the following structure:
{
"error": {
"code": 404,
"message": "Recovery request not found"
}
}
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - Invalid parameters or missing required fields |
| 401 | Unauthorized - Invalid or missing Bearer token |
| 404 | Not Found - Resource not found |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error - Something went wrong on the server |
Common Error Messages
| Message | Description |
|---|---|
| Recovery request not found | The requested recovery ID does not exist |
| Invalid signature | The signature verification failed |
| Guardian not found | The signer is not a registered guardian |
| Insufficient signatures | Not enough guardian signatures collected |
| Rate limit exceeded | Too many requests, please try again later |