> ## Documentation Index
> Fetch the complete documentation index at: https://developers.circle.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# How-to: Recover an account

> Restore a user's wallet access after they forget their PIN, using their pre-set security questions.

Restore a user's wallet access when they forget their PIN. The user answers the
security questions they set during sign-up, then sets a new PIN. The answers to
security questions are the user's responsibility to remember.

<Warning>
  Circle has no way to recover an account when both the PIN and the security
  answers are lost. The user is permanently locked out of their account and all
  wallets and assets.
</Warning>

## Prerequisites

Before you begin, ensure that you've:

* Obtained a Circle Developer API key from the
  [Circle Console](https://console.circle.com/).
* Completed the
  [Build a wallet app](/wallets/user-controlled/build-a-wallet-app) tutorial
  with the PIN method, which sets up a user-controlled wallet, stores the user's
  `userId`, and configures the user's security questions.
* Integrated a user-controlled wallet client-side SDK in your app to walk the
  user through the recovery challenge: [Web SDK](/sdks/user-controlled/web-sdk),
  [iOS SDK](/sdks/user-controlled/ios-sdk),
  [Android SDK](/sdks/user-controlled/android-sdk), or
  [React Native SDK](/sdks/user-controlled/react-native-sdk).
* Installed the user-controlled wallet server-side SDK in your backend to create
  the recovery challenge: [Node.js](/sdks/user-controlled-wallets-nodejs-sdk) or
  [Python](/sdks/user-controlled-wallets-python-sdk).

## Steps

<Steps>
  <Step title="Acquire a session token">
    Request a 60-minute session token for the user. The token authorizes the
    recovery challenge later in the flow.

    <CodeGroup>
      ```ts Node.js SDK theme={null}
      import { initiateUserControlledWalletsClient } from "@circle-fin/user-controlled-wallets";

      const client = initiateUserControlledWalletsClient({
        apiKey: process.env.CIRCLE_API_KEY!,
      });

      const response = await client.createUserToken({
        userId: "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
      });

      const userToken: string = response.data!.userToken;
      const encryptionKey: string = response.data!.encryptionKey;
      ```

      ```python Python SDK theme={null}
      from circle.web3 import user_controlled_wallets, utils

      client = utils.init_user_controlled_wallets_client(api_key="<CIRCLE_API_KEY>")
      api_instance = user_controlled_wallets.UsersAndPinsApi(client)

      request = user_controlled_wallets.GenerateUserTokenRequest.from_dict(
          {"userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463"}
      )
      response = api_instance.get_user_token(request)

      user_token = response.data.user_token
      encryption_key = response.data.encryption_key
      ```
    </CodeGroup>
  </Step>

  <Step title="Initialize the recovery challenge">
    Use the `userToken` to create a recovery challenge. The SDK returns a
    `challengeId` that your client-side SDK uses to walk the user through answering
    their security questions and setting a new PIN.

    <CodeGroup>
      ```ts Node.js SDK theme={null}
      const response = await client.restoreUserPin({
        userToken,
      });

      const challengeId: string = response.data!.challengeId;
      ```

      ```python Python SDK theme={null}
      request = user_controlled_wallets.RestorePinRequest.from_dict({
          "idempotencyKey": str(uuid.uuid4()),
      })
      response = api_instance.create_user_pin_restore_challenge(user_token, request)

      challenge_id = response.data.challenge_id
      ```
    </CodeGroup>

    Include an `idempotencyKey` (a UUID) on the call to safely retry the request
    without creating duplicate challenges. See
    [Idempotent requests](/api-reference/idempotent-requests) for details on
    idempotency key usage.
  </Step>

  <Step title="Have the user answer their security questions">
    Pass the `userToken`, `encryptionKey`, and `challengeId` to your client-side
    SDK. The SDK presents the recovery UI to the user, who:

    1. Answers their pre-set security questions.
    2. Enters and confirms a new PIN.

    The SDK completes the challenge with Circle.
  </Step>

  <Step title="Check the challenge status">
    Confirm the recovery completed. Use webhooks (push) or polling (pull) to detect
    when the challenge reaches a terminal status: `COMPLETED`, `FAILED`, or
    `EXPIRED`. A `COMPLETED` status means the account was successfully recovered and
    the new PIN is active.

    <Tabs>
      <Tab title="Webhook">
        Subscribe to user challenge notifications and listen for the event matching your
        `challengeId`. The notification includes the challenge `status` and `type`
        (`RESTORE_PIN` for account recovery).

        ```json Webhook notification theme={null}
        {
          "subscriptionId": "d4c07d5f-f05f-4fe4-853d-4dd434806dfb",
          "notificationId": "acab8c14-92ae-481a-8335-6eb5271da014",
          "notificationType": "challenges.initialize",
          "notification": {
            "id": "c4d1da72-111e-4d52-bdbf-2e74a2d803d5",
            "userId": "2f1dcb5e-312a-4b15-8240-abeffc0e3463",
            "type": "RESTORE_PIN",
            "status": "COMPLETE",
            "correlationIds": ["54399e5a-1bf6-4921-9559-10c1115678cd"],
            "errorCode": 0,
            "errorMessage": ""
          },
          "timestamp": "2026-01-15T14:33:17.785131449Z",
          "version": 2
        }
        ```

        For webhook setup, see [Webhooks](/api-reference/webhooks).
      </Tab>

      <Tab title="Polling">
        Poll `getUserChallenge` until the challenge reaches a terminal status.

        <CodeGroup>
          ```ts Node.js SDK theme={null}
          async function pollChallenge(challengeId: string, userToken: string) {
            const TERMINAL = new Set(["COMPLETED", "FAILED", "EXPIRED"]);
            for (let attempt = 0; attempt < 30; attempt++) {
              const response = await client.getUserChallenge({ userToken, challengeId });
              const status = response.data!.challenge!.status;
              if (TERMINAL.has(status)) return response.data!.challenge!;
              await new Promise((resolve) => setTimeout(resolve, 2000));
            }
            throw new Error("Challenge polling timed out");
          }
          ```

          ```python Python SDK theme={null}
          import time

          def poll_challenge(challenge_id: str, user_token: str):
              terminal = {"COMPLETED", "FAILED", "EXPIRED"}
              for _ in range(30):
                  response = api_instance.get_user_challenge(user_token, challenge_id)
                  status = response.data.challenge.status
                  if status in terminal:
                      return response.data.challenge
                  time.sleep(2)
              raise TimeoutError("Challenge polling timed out")
          ```
        </CodeGroup>
      </Tab>
    </Tabs>

    For a full list of possible statuses, see
    [Transaction states and errors](/api-reference/wallets/transaction-states-and-errors).
  </Step>
</Steps>

## Error handling

Handle these common failure cases when integrating account recovery:

* **Expired session token (error code `155104`):** The `userToken` from Step 1
  expires after 60 minutes. If you get this error, request a new session token
  and retry.
* **Incorrect security answers (error code `155115`):** The user's answers must
  match what they set during sign-up. After three incorrect attempts, recovery
  locks for 30 minutes (`155120`).
* **Security questions not set (error code `155111`):** The user must have set
  security questions during sign-up to use account recovery. If they haven't,
  recovery isn't available.
* **Security questions already used:** Security questions can be set only once
  during sign-up and can't be reset (`155108`). If the user can't remember their
  answers, no recovery path exists.

For a complete error code reference, see
[Wallets error codes](/api-reference/wallets/error-codes).
