How to list passkeys through the BaaS API.
Management
List Passkeys for a Wallet
Endpoint: GET /baas/passkeys?walletAddress=0x...
Required scope: passkey:manage
Lists on-chain recovery passkeys for a specific wallet. The walletAddress query parameter is required.
curl "https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/passkeys?walletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18" \
-H "x-api-key: pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4"{
"statusCode": 200,
"content": {
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"passkeys": [
{
"qx": "0x1f2e3d4c5b6a79889786766554433221100ffeeddccbbaa99887766554433221",
"qy": "0xa0b1c2d3e4f50617283940516273849506172839405162738495061728394050"
}
]
}
}Each passkey is represented by its P-256 public key coordinates (qx and qy). These are the same coordinates used when creating the wallet.
Recovering a Wallet
Registering spares is only half of it. This is the flow that spends one: handing the wallet to a new key when the device holding the owner passkey is gone.
It has a different shape from adding or removing a spare. Those are calls the wallet makes on itself, signed by the owner passkey, and submitted through POST /baas/transactions/execute. A recovery is Relayer.relayChangeOwner, signed by a recovery passkey — the owner key is exactly what is missing — and has its own execute endpoint.
1. Create the credential that will own the wallet
An ordinary navigator.credentials.create() on your origin, on the device your user still has. Extract its P-256 coordinates.
2. Prepare
Endpoint: POST /baas/wallets/prepare-recover · Scope: passkey:manage
curl -X POST "https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/wallets/prepare-recover" -H "x-api-key: pk_live_..." -H "content-type: application/json" -d '{
"walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"newQx": "0x1f2e3d4c5b6a79889786766554433221100ffeeddccbbaa99887766554433221",
"newQy": "0xa0b1c2d3e4f50617283940516273849506172839405162738495061728394050"
}'{
"statusCode": 200,
"content": {
"success": true,
"changeOwnerHash": "0x8f3a...",
"expiration": "1753929000",
"fee": "1000000000000000000",
"feeCoinAddress": "0x...",
"feeWithdrawAddress": "0x...",
"recoveryPasskeyCount": 2
}
}3. Sign with a recovery passkey
A normal navigator.credentials.get() using changeOwnerHash as the challenge. You do not need to say which spare — the contract checks the signature against its own registered set, so let the platform authenticator choose.
4. Execute
Endpoint: POST /baas/wallets/execute-recover
Echo the fee and expiration fields back unchanged. They are part of what was signed — change any of them and the signature no longer matches.
{
"walletAddress": "0x742d35Cc...",
"newQx": "0x1f2e...", "newQy": "0xa0b1...",
"expiration": "1753929000",
"fee": "1000000000000000000",
"feeCoinAddress": "0x...", "feeWithdrawAddress": "0x...",
"clientDataJSON": "{"type":"webauthn.get",...}",
"authenticatorData": "0x49960de5...",
"signature": "0x304502..."
}The wallet address does not change. Only the key that opens it does.
What your API key cannot do
Prepare a rotation and stop there. The relay carries a signature from a credential registered on-chain, held on your user's device. Holding passkey:manage lets you start this flow; it does not let you finish one. That guarantee is the contract's, not ours — which is what makes the scope safe to grant.
The prepared hash is valid for 600 seconds, longer than an ordinary transfer: your user is on an unfamiliar device, creating one credential and then authenticating with another, usually while reading your instructions. If it does expire, execute-recover refuses before relaying and says nothing was charged, rather than spending their fee on a transaction that was always going to revert.
The case this cannot fix
A wallet with no spare registered. Adding one requires the owner passkey, so the moment it is lost the wallet is closed for good — to you, to us, to everyone. prepare-recover returns 400 and says so rather than handing back a hash nothing can sign. Ask your users for a second device at setup, not after.
Best Practices
- Encourage multiple passkeys. Users should have at least 2 (phone + laptop) for redundancy.
- Show the spares in your UI, and let users remove one. A count is not enough: a spare that can be registered but never revoked is a one-way door, and a lost or sold device stays able to take the wallet.
- Build the recovery path before you need it. Registering spares your users cannot actually use is worse than offering nothing, because they believe they are covered.
- Use webhooks. Subscribe to transaction events to track wallet activity.