> For the complete documentation index, see [llms.txt](https://bound-1.gitbook.io/bound-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bound-1.gitbook.io/bound-docs/developer-guide/api-reference/account.md).

# Account

## GET /api/account

> Admin: list accounts (paginated)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/account":{"get":{"operationId":"AccountController_paginate","summary":"Admin: list accounts (paginated)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## GET /api/account/me

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/me":{"get":{"operationId":"AccountController_getMe","parameters":[],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## GET /api/account/referral-code/{code}/verify

> Verify a referral code is valid (public)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/referral-code/{code}/verify":{"get":{"operationId":"AccountController_verifyReferralCode","summary":"Verify a referral code is valid (public)","parameters":[{"name":"code","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## Change password step 1 — get current password challenge

> Returns sessionId, salt, and serverPublic (B) to begin SRP proof of the existing password.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/srp/change-password/init":{"post":{"operationId":"AccountController_srpChangePasswordInit","summary":"Change password step 1 — get current password challenge","description":"Returns sessionId, salt, and serverPublic (B) to begin SRP proof of the existing password.","parameters":[],"responses":{"200":{"description":"Returns sessionId, salt, and serverPublic (B)"}},"tags":["account"]}}}}
```

## Change password step 2 — verify old password and set new credentials

> Client proves knowledge of the old password via M1, then submits new salt, verifier, and encrypted blob atomically.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/srp/change-password/verify":{"post":{"operationId":"AccountController_srpChangePasswordVerify","summary":"Change password step 2 — verify old password and set new credentials","description":"Client proves knowledge of the old password via M1, then submits new salt, verifier, and encrypted blob atomically.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordDto"}}}},"responses":{"200":{"description":"Password changed — returns serverProof (M2) for client verification"}},"tags":["account"]}}},"components":{"schemas":{"ChangePasswordDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID returned by POST /auth/srp/change-password/init"},"clientPublic":{"type":"string","description":"Client ephemeral public key A = g^a mod N (base64)"},"clientProof":{"type":"string","description":"Client proof M1 = H(H(N)⊕H(g) || H(I) || salt || A || B || K) (base64)"},"newSrpSalt":{"type":"string","description":"New SRP salt generated client-side (base64, 32 bytes)"},"newSrpVerifier":{"type":"string","description":"New SRP verifier v = g^x mod N (base64)"},"newEncryptedBlob":{"type":"string","description":"Keystore blob re-encrypted with new derived key (base64, max 13708 chars)"},"newPasswordHint":{"type":"string","description":"New password hint"}},"required":["sessionId","clientPublic","clientProof","newSrpSalt","newSrpVerifier","newEncryptedBlob"]}}}}
```

## Rotate auth wallet — atomically swap the keystore to a new external wallet

> Wallet-auth accounts only. Client re-encrypts the blob with the NEW wallet derivation key client-side; server verifies the NEW wallet control proof (fresh nonce) and atomically replaces encryptedBlob + auth-wallet fields. Old-wallet derivation signature is never sent.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"RotateAuthWalletDto":{"type":"object","properties":{"address":{"type":"string","description":"Address of the NEW auth wallet"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string","description":"Public key of the NEW auth wallet (verifies EVM/Solana signatures)"},"provider":{"type":"string","description":"metamask | phantom | xverse | okx | unisat"},"challengeId":{"type":"string","description":"challengeId from GET /auth/wallet/challenge?address=<newAddress>"},"signature":{"type":"string","description":"NEW wallet signature over the issued server nonce (control proof)"},"encryptedBlob":{"type":"string","description":"Keystore blob re-encrypted client-side with the NEW wallet derivation key"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]}}},"paths":{"/api/account/wallet/rotate":{"post":{"operationId":"AccountController_rotateAuthWallet","summary":"Rotate auth wallet — atomically swap the keystore to a new external wallet","description":"Wallet-auth accounts only. Client re-encrypts the blob with the NEW wallet derivation key client-side; server verifies the NEW wallet control proof (fresh nonce) and atomically replaces encryptedBlob + auth-wallet fields. Old-wallet derivation signature is never sent.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateAuthWalletDto"}}}},"responses":{"200":{"description":"Auth wallet rotated"},"400":{"description":"Method not allowed, signature invalid, challenge invalid, taken, or not a wallet keystore"},"401":{"description":"Unauthorized"}},"tags":["account"]}}}}
```

## POST /api/account/2fa/setup

> Set up TOTP 2FA — generate a new secret and return the otpauth URI

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/2fa/setup":{"post":{"operationId":"AccountController_setupTotp","summary":"Set up TOTP 2FA — generate a new secret and return the otpauth URI","parameters":[],"responses":{"200":{"description":"Returns otpauthUri for QR code rendering"}},"tags":["account"]}}}}
```

## POST /api/account/2fa/toggle

> Enable or disable TOTP 2FA — requires a valid OTP code

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/2fa/toggle":{"post":{"operationId":"AccountController_toggleTotp","summary":"Enable or disable TOTP 2FA — requires a valid OTP code","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpToggleDto"}}}},"responses":{"200":{"description":"2FA state toggled"},"400":{"description":"Invalid OTP code or setup not completed"}},"tags":["account"]}}},"components":{"schemas":{"TotpToggleDto":{"type":"object","properties":{}}}}}
```

## PATCH /api/account/avatar

> Set an inscription as the account avatar

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/avatar":{"patch":{"operationId":"AccountController_setAvatar","summary":"Set an inscription as the account avatar","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAvatarDto"}}}},"responses":{"200":{"description":"Avatar updated"},"400":{"description":"Inscription not found or not owned by caller"}},"tags":["account"]}}},"components":{"schemas":{"SetAvatarDto":{"type":"object","properties":{"inscriptionId":{"type":"string","description":"The inscriptionId to set as avatar. Pass null or omit to remove the avatar.","nullable":true}}}}}}
```

## DELETE /api/account/default-token

> Remove a default token for a slot

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/default-token":{"delete":{"operationId":"AccountController_removeDefaultToken","summary":"Remove a default token for a slot","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveDefaultTokenDto"}}}},"responses":{"200":{"description":"Default token removed successfully"},"400":{"description":"Invalid slot or tokenId"},"401":{"description":"Unauthorized"}},"tags":["account"]}}},"components":{"schemas":{"RemoveDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## PATCH /api/account/default-token

> Set a default token for a slot

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/default-token":{"patch":{"operationId":"AccountController_setDefaultToken","summary":"Set a default token for a slot","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetDefaultTokenDto"}}}},"responses":{"200":{"description":"Default token set successfully"},"400":{"description":"Token not found, unsupported chain, or token invalid for slot"},"401":{"description":"Unauthorized"}},"tags":["account"]}}},"components":{"schemas":{"SetDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## Initiate email change — send verification link to new address

> Queues a verification email to the new address and a notice to the current address. Returns \`{ srpSession }\` for SRP accounts or \`{ webauthnChallenge }\` for passkey accounts — only one field is present at a time.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/change-email/init":{"post":{"operationId":"AccountController_changeEmailInit","summary":"Initiate email change — send verification link to new address","description":"Queues a verification email to the new address and a notice to the current address. Returns `{ srpSession }` for SRP accounts or `{ webauthnChallenge }` for passkey accounts — only one field is present at a time.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeEmailInitDto"}}}},"responses":{"200":{"description":"Returns srpSession (SRP accounts) or webauthnChallenge (passkey accounts)"},"400":{"description":"New email already taken or same as current email"},"429":{"description":"Rate limit exceeded"}},"tags":["account"]}}},"components":{"schemas":{"ChangeEmailInitDto":{"type":"object","properties":{"newEmail":{"type":"string","description":"New email address to associate with the account"}},"required":["newEmail"]}}}}
```

## Confirm email change using verification token

> Finalises the email update. SRP accounts: \`srpSessionId\`, \`clientPublic\`, \`clientProof\`, and \`newSrpVerifier\` are conditionally required. \`srpSession\` from the init response provides \`sessionId\`, \`salt\`, and \`serverPublic\`. Passkey accounts: \`credentialId\`, \`challengeId\`, and \`webauthnAssertion\` are conditionally required. \`webauthnChallenge\` from the init response provides \`challengeId\` and \`challenge\`.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.bound.exchange","description":"Production"}],"paths":{"/api/account/change-email/verify":{"post":{"operationId":"AccountController_changeEmailVerify","summary":"Confirm email change using verification token","description":"Finalises the email update. SRP accounts: `srpSessionId`, `clientPublic`, `clientProof`, and `newSrpVerifier` are conditionally required. `srpSession` from the init response provides `sessionId`, `salt`, and `serverPublic`. Passkey accounts: `credentialId`, `challengeId`, and `webauthnAssertion` are conditionally required. `webauthnChallenge` from the init response provides `challengeId` and `challenge`.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeEmailVerifyDto"}}}},"responses":{"200":{"description":"Email updated successfully"},"400":{"description":"Invalid or expired token, email mismatch, or missing re-auth fields"}},"tags":["account"]}}},"components":{"schemas":{"ChangeEmailVerifyDto":{"type":"object","properties":{"token":{"type":"string","description":"Verification token from the change-email email link"},"email":{"type":"string","description":"New email address being verified (cross-verified against the token)"},"srpData":{"description":"SRP re-auth data — required when the account has an SRP keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailSrpDataDto"}]},"passkeyData":{"description":"Passkey re-auth data — required when the account has a passkey keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailPasskeyDataDto"}]}},"required":["token","email"]},"ChangeEmailSrpDataDto":{"type":"object","properties":{"newSrpVerifier":{"type":"string","description":"New SRP verifier recomputed as SRP(newEmail, currentPassword)"},"sessionId":{"type":"string","description":"SRP session ID from the init response"},"clientPublic":{"type":"string","description":"SRP client public ephemeral"},"clientProof":{"type":"string","description":"SRP client proof"}},"required":["newSrpVerifier","sessionId","clientPublic","clientProof"]},"ChangeEmailPasskeyDataDto":{"type":"object","properties":{"credentialId":{"type":"string","description":"Credential ID of the registered passkey"},"challengeId":{"type":"string","description":"Challenge ID from the init response"},"webauthnAssertion":{"description":"WebAuthn assertion from the passkey device","allOf":[{"$ref":"#/components/schemas/ChangeEmailWebAuthnAssertionDto"}]}},"required":["credentialId","challengeId","webauthnAssertion"]},"ChangeEmailWebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string","description":"Base64-encoded authenticator data"},"clientDataJson":{"type":"string","description":"Base64-encoded client data JSON"},"signature":{"type":"string","description":"Base64-encoded DER signature"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```
