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.

bash
curl "https://gateway.dev.passkallet.com/sepolia/api/v1/passkallet/baas/passkeys?walletAddress=0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18" \
  -H "x-api-key: pk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4"
json
{
  "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

bash
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"
  }'
json
{
  "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.

json
{
  "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

  1. Encourage multiple passkeys. Users should have at least 2 (phone + laptop) for redundancy.
  2. 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.
  3. 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.
  4. Use webhooks. Subscribe to transaction events to track wallet activity.