Skip to main content

Safe Recovery UX API

Use the Recovery UX API to collect guardian signatures, submit recovery transactions, track requests, and manage email/SMS alerts for Safe accounts. It works with the Social Recovery Module.

For email/SMS verification that produces a guardian signature, use the Email/SMS Recovery API. For an explanation of the full recovery process, see the service overview.

Before You Start​

  • Enable the Social Recovery Module and configure guardians and a recovery threshold on the Safe account.
  • Request service access to obtain your service URL and Bearer token.
  • Use the Recovery Flow Guide for a TypeScript integration example.

Authentication​

Include Authorization: Bearer YOUR_BEARER_TOKEN in every request. Replace https://yourcompany.recovery.candide.dev with your service URL.

Requests that create or approve a recovery also require a guardian signature over the recovery data. Alert subscription management requires an owner-signed SIWE message. These signatures authorize the account action; the Bearer token authenticates access to the API.

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.

Recovery Workflow​

  1. Create: Submit a request with the first guardian's signature using /recoveries/create.
  2. Approve: Other guardians check the request's emoji sequence with the account owner through a trusted channel, then submit signatures using /recoveries/sign.
  3. Execute: Once the guardian threshold is met, call /recoveries/execute to submit the request on-chain and start the grace period.
  4. Finalize: After the grace period, call /recoveries/finalize to apply the new owners and signing threshold. The current owner can cancel the recovery during the grace period.

The service sponsors gas for execution and finalization when configured, but it does not submit them on its own: your application must call both endpoints. Guardians can submit signatures off-chain without holding funds. Use the lookup endpoints to monitor progress; a successful submission does not by itself mean ownership has changed. See request states.

newThreshold is the signing threshold for the new Safe owners after recovery. The existing guardian threshold determines how many guardians must approve the recovery.

Endpoints​

MethodEndpointPurpose
POST/recoveries/createCreate a request with a guardian signature.
POST/recoveries/signAdd another guardian's signature.
POST/recoveries/executeSubmit execution once the guardian threshold is met.
POST/recoveries/finalizeSubmit finalization after the grace period.
GET/recoveries/fetchByIdFetch one request by its service ID.
GET/recoveries/fetchByAddressFetch requests for an account, chain, and recovery nonce.
GET/recoveries/listByAddressList requests with filters, sorting, and pagination.
POST/alerts/subscribeCreate an inactive alert subscription and send a verification code.
POST/alerts/activateVerify the code and activate the subscription.
GET/alerts/subscriptionsList active subscriptions for an account and owner.
POST/alerts/unsubscribeRemove a subscription.

Recoveries​

Create Recovery Request​

Create a request proposing new owners and their signing threshold. The signer must be a guardian of the Safe account, and signature must approve the recovery data. The service stores this first signature with the request.

POST /recoveries/create​

curl -X POST \
https://yourcompany.recovery.candide.dev/recoveries/create \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba",
"newOwners": ["0x41153290c995c8c4410d50f95D87ee86A1B07eeC", "0xB97A1C3993A551f0Febf030539630ACb77E6832D"],
"newThreshold": 2,
"chainId": 1,
"signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'

Collect a guardian signature​

Add a guardian signature to an existing recovery request. The signature must approve the same recovery data as the request.

POST /recoveries/sign​

curl -X POST \
https://yourcompany.recovery.candide.dev/recoveries/sign \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"id": "123456789",
"signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
}'

Execute a recovery by ID​

Submit the execution transaction for a request that has met the guardian threshold. Execution starts the grace period; it does not change ownership.

POST /recoveries/execute​

curl -X POST \
https://yourcompany.recovery.candide.dev/recoveries/execute \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"id": "123456789"
}'

Finalize recovery by ID​

Submit the finalization transaction after the grace period has ended. Once finalized on-chain, the new owners and signing threshold take effect.

POST /recoveries/finalize​

curl -X POST \
https://yourcompany.recovery.candide.dev/recoveries/finalize \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"id": "123456789"
}'

Fetch recovery by address​

Fetch all discoverable requests matching a Safe account, chain, and recovery module nonce. The response is an array, which can be empty. Supply nonce as a hexadecimal string.

GET /recoveries/fetchByAddress​

curl -G "https://yourcompany.recovery.candide.dev/recoveries/fetchByAddress" \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
--data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \
--data-urlencode "chainId=1" \
--data-urlencode "nonce=0x1"

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​

curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
--data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \
--data-urlencode "chainId=1" \
--data-urlencode "limit=20" \
--data-urlencode "offset=0"
When to use fetchByAddress vs listByAddress
  • /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 one request using the id returned when it was created.

GET /recoveries/fetchById​

curl -G "https://yourcompany.recovery.candide.dev/recoveries/fetchById" \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
--data-urlencode "id=123456789"

Alerts​

Alert subscriptions are separate from email/SMS recovery registrations. Subscribing to alerts does not add a guardian or enable email/SMS recovery.

Create a subscription, then activate it with the code sent to the selected channel. Subscriptions monitor the Safe account across supported chains with alerts enabled. The chainId identifies the network used to verify the signing owner; subscription creation also checks that this owner belongs to the Safe on that chain.

The owner signs the SIWE message for subscription creation, lookup, and removal. See SIWE signing and the Recovery Alerts Guide.

Create an Alert Subscription​

POST /alerts/subscribe​

Create an inactive subscription and send a verification code to the target email address or phone number. Save the returned subscriptionId and activate it using /alerts/activate.

curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/subscribe \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"account": "0x...",
"owner": "0x...",
"chainId": 1,
"channel": "email",
"target": "user@example.com",
"message": "siwe(chainId, statement(account, channel, target))",
"signature": "sign(message)"
}'
  • account: The Safe account to monitor.
  • owner: The Safe owner authorizing the subscription and signing the SIWE message.
  • chainId: The supported chain used to verify the owner and signature. Subscriptions cover all supported chains with alerts enabled.
  • channel: Either "email" or "sms" (defines the delivery channel).
  • target: The email address or phone number that receives alerts.
  • signature: The signature from owner over the complete SIWE message.
  • message: The complete SIWE message containing this exact statement:
I agree to receive Social Recovery Module alert notifications for {{account}} on all supported chains sent to {{target}} (via {{channel}})

Replace {{account}} with the lowercase Safe address, {{target}} with the destination, and {{channel}} with email or sms.

See SIWE signing to construct the message and signature.

Activate an Alert Subscription​

POST /alerts/activate​

Submit the verification code sent during subscription creation. Alerts begin after activation.

curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/activate \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"subscriptionId": "unique-subscription-id",
"challenge": "123456"
}'
  • subscriptionId: The unique ID received in the subscription response.
  • challenge: The code received via email/SMS.

List Active Alert Subscriptions​

Fetch active subscriptions for the specified Safe account and signing owner.

GET /alerts/subscriptions​

curl -G "https://yourcompany.recovery.candide.dev/alerts/subscriptions" \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
--data-urlencode "account=0x..." \
--data-urlencode "owner=0x..." \
--data-urlencode "chainId=1" \
--data-urlencode "message=siwe(chainId, statement)" \
--data-urlencode "signature=sign(message)"
  • account: The Safe account being monitored.
  • owner: The owner whose subscriptions to retrieve and who signs the SIWE message.
  • chainId: The supported chain used to verify the owner and signature. Subscriptions cover all supported chains with alerts enabled.
  • signature: The signature from owner over the complete SIWE message.
  • message: The complete SIWE message containing this exact statement:
I request to retrieve all Social Recovery Module alert subscriptions linked to my account

See SIWE signing to construct the message and signature.

Remove an Alert Subscription​

Remove the subscription identified by subscriptionId. Include the owner's SIWE message and signature.

POST /alerts/unsubscribe​

curl -X POST \
https://yourcompany.recovery.candide.dev/alerts/unsubscribe \
-H 'Authorization: Bearer YOUR_BEARER_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"subscriptionId": "unique-subscription-id",
"owner": "0x...",
"chainId": 1,
"message": "siwe(chainId, statement)",
"signature": "sign(message)"
}'
  • subscriptionId: The subscription to remove.
  • owner: The owner signing the request.
  • chainId: The supported chain used to verify the signature.
  • message: The complete SIWE message containing the statement below.
  • signature: The signature from owner over that message.
I request to unsubscribe from all Social Recovery Module alert subscriptions linked to my account

Use this exact statement even when removing one subscription. The subscriptionId selects the subscription to remove.

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": "Recovery request not found"
}

HTTP Status Codes​

CodeDescription
200Success
400Bad Request - Invalid parameters, missing required fields, or an invalid guardian signature
401Unauthorized - Invalid or missing Bearer token
403Forbidden - SIWE signature verification failed, challenge invalid or expired, sponsorship not enabled for the network, or the request is not ready to execute (wrong nonce or insufficient signatures)
404Not Found - Resource not found
409Conflict - The alert subscription already exists
429Too Many Requests - Rate limit exceeded
500Internal 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, create a new alert subscription and verify its new code.
  • Missing resource: Use the ID returned by the corresponding creation endpoint.
  • Rate limit: Wait before retrying the request.