Skip to main content

Exception Handling

Overview

Errors raised during embedded-wallet operations come from three layers:

  1. Server-side, RFC 9457 Problem Details — JSON responses returned by the Keyban API or by the cryptographic signers behind it. Identified by a type URI and a stable title.
  2. SDK-side, runtime errors — JavaScript exceptions thrown by @keyban/sdk-base and @keyban/sdk-react before or after the network call. Identified by a string code from the SdkErrorCode enum.
  3. Generic HTTP errors — standard 4xx/5xx responses without a Keyban-specific type (validation, authentication, rate limiting at the edge). Use the HTTP status code only.

RFC 9457 obsoletes RFC 7807 but is wire-compatible — clients written against 7807 do not need to change.

Problem Details envelope

FieldRequiredDescription
typeyesURI reference identifying the problem type. Dereferenceable to this page (see anchors below).
titleyesShort, human-readable summary, stable across occurrences (modulo localization).
statusyesHTTP status code.
detailyesHuman-readable explanation specific to this occurrence.
instanceoptionalURI reference for the specific occurrence (e.g. the request path with the relevant id).

Extension members may be present (e.g. keyid) to convey context. RFC 9457 §3.2 defines how extension members are namespaced; consumers should ignore unknown keys.

SdkError envelope

SdkError extends KeybanError, both exported from @keyban/sdk-base. Each instance carries:

  • a name ("SdkError"),
  • a code from the SdkErrorCode enum (string discriminator),
  • a message (the default text shown in the catalogue, overridable),
  • an optional cause (the underlying error, for example a Viem BaseError or a fetch failure).

Match on error instanceof SdkError && error.code === SdkErrorCode.X rather than on the message string.

Client strategy

Recommended client behaviour by category. Apply the row that matches the actual response.

SourceSymptomRecommended client behaviour
HTTP 400 Bad RequestMalformed payload (JSON, base64).Fail-fast. Log the request, fix the client encoding bug, do not retry — the next attempt will fail identically.
HTTP 401 Unauthorized / 403 ForbiddenMissing or invalid API key, expired session, organization permission denied.Fail-fast. Re-authenticate or surface the permission error to the user.
HTTP 404 Not Found (key/round)Key share or DKG round-1 state missing on the server.Treat as a state-reset signal. Recreate the share for a missing key, restart the DKG protocol from round 0 for a missing round. Do not retry the same call.
HTTP 409 ConflictResource already exists or current state forbids the operation (e.g. wallet still deploying).Fail-fast. Reconcile client state by re-reading the resource.
HTTP 422 Unprocessable EntitySemantic validation failure (e.g. billing item misconfigured upstream).Fail-fast. The fix is operational, not retry-able client-side.
HTTP 429 Too Many RequestsQuota or rate limit reached.Surface the quota error to the user; retry only after the operator has raised the quota or the period has rolled over. Use exponential backoff if the limit is a sliding rate.
HTTP 500 (FROST internal)Threshold-signing protocol failure.Fail-fast. Capture keyid and instance and escalate to support — the protocol cannot continue with the current state.
HTTP 500 (infrastructure)Transient backend (Redis, serialization, base64).Retry with exponential backoff (e.g. 3 attempts, 250 ms → 1 s → 4 s). Surface to the user only if all attempts fail.
SdkError (validation code)Address/amount/nft-standard rejected before the request leaves the SDK.Fail-fast. Show the input back to the user with the reason.
SdkError (RelayFailed)Relay endpoint returned non-2xx; cause carries the underlying message (often an on-chain revert).Inspect cause. Revert messages are usually deterministic — fail-fast. Network/RPC timeouts may be retried once.
SdkError (Unexpected)Runtime invariant violated (backend payload inconsistent with schema).Fail-fast. Report to support with the request context.

Server-side errors (Problem Details)

FROST signer errors

Returned by the threshold-signing services that back DKG and signing operations.

#TitleSlugHTTPEmitted by
1Base64 decode errorbase64-decode-error400 Bad RequestECDSA-Schnorr, EdDSA
2Frost request json invalidfrost-request-json-invalid400 Bad RequestECDSA-Schnorr, EdDSA
3No DKG round foundno-dkg-round-found404 Not FoundECDSA-Schnorr, EdDSA
4No key foundno-key-found404 Not FoundECDSA-Schnorr, EdDSA
5Frost internal errorfrost-internal-error500 Internal Server ErrorECDSA-Schnorr, EdDSA
6Frost Poseidon internal errorfrost-poseidon-internal-error500 Internal Server ErrorECDSA-Schnorr
7Redis errorredis-error500 Internal Server ErrorECDSA-Schnorr, EdDSA
8Serialize errorserialize-error500 Internal Server ErrorECDSA-Schnorr, EdDSA

Base64 decode error

  • HTTP status: 400 Bad Request
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#base64-decode-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#base64-decode-error",
"title": "Base64 decode error",
"status": 400,
"detail": "<error description>"
}

Frost request json invalid

  • HTTP status: 400 Bad Request
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#frost-request-json-invalid
  • Extension members: url
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-request-json-invalid",
"title": "Frost request json invalid",
"status": 400,
"detail": "<error description>",
"url": "/signers/ecdsa-schnorr/dkg/round1"
}

No DKG round found

  • HTTP status: 404 Not Found
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#no-dkg-round-found
  • Extension members: keyid
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#no-dkg-round-found",
"title": "No DKG round found",
"status": 404,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…",
"keyid": "0x4f8b…"
}

No key found

  • HTTP status: 404 Not Found
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#no-key-found
  • Extension members: keyid
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#no-key-found",
"title": "No key found",
"status": 404,
"detail": "Cannot find an ecdsa key with id: <keyid>",
"keyid": "0x4f8b…"
}

Frost internal error

  • HTTP status: 500 Internal Server Error
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#frost-internal-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-internal-error",
"title": "Frost internal error",
"status": 500,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…"
}

Frost Poseidon internal error

  • HTTP status: 500 Internal Server Error
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#frost-poseidon-internal-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#frost-poseidon-internal-error",
"title": "Frost Poseidon internal error",
"status": 500,
"detail": "<error description>",
"instance": "/signers/ecdsa-schnorr/dkg/round1/0x4f8b…"
}

Redis error

  • HTTP status: 500 Internal Server Error
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#redis-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#redis-error",
"title": "Redis error",
"status": 500,
"detail": "An error occurred while accessing Redis"
}

Serialize error

  • HTTP status: 500 Internal Server Error
  • Type URI: https://docs.keyban.io/products/embedded-wallet/exceptions#serialize-error
{
"type": "https://docs.keyban.io/products/embedded-wallet/exceptions#serialize-error",
"title": "Serialize error",
"status": 500,
"detail": "An error occurred while serializing"
}

Backend service errors

Returned by the public Keyban API when org-level constraints (quota, plan features, network allowlist) reject the operation. The current implementations do not assign a type URI — clients should match on title and status.

#TitleSlugHTTP
1Feature not allowedfeature-not-allowed403 Forbidden
2Network not allowednetwork-not-allowed403 Forbidden
3Quota active users limit exceededquota-active-users-limit-exceeded429 Too Many Requests
4Quota application limit exceededquota-application-limit-exceeded429 Too Many Requests
5Quota DPP limit exceededquota-dpp-limit-exceeded429 Too Many Requests

Feature not allowed

  • HTTP status: 403 Forbidden
{
"type": "about:blank",
"title": "Feature not allowed",
"status": 403,
"detail": "One or more requested features are not allowed for your organization. Contact your administrator."
}

Network not allowed

  • HTTP status: 403 Forbidden
{
"type": "about:blank",
"title": "Network not allowed",
"status": 403,
"detail": "The requested network is not allowed for your organization. Contact your administrator."
}

Quota active users limit exceeded

  • HTTP status: 429 Too Many Requests
{
"type": "about:blank",
"title": "Quota active users limit exceeded",
"status": 429,
"detail": "You have reached your monthly active users quota. Contact your administrator to increase your limit."
}

Quota application limit exceeded

  • HTTP status: 429 Too Many Requests
{
"type": "about:blank",
"title": "Quota application limit exceeded",
"status": 429,
"detail": "You have reached your application limit. Contact your administrator to increase your quota."
}

Quota DPP limit exceeded

  • HTTP status: 429 Too Many Requests
{
"type": "about:blank",
"title": "Quota DPP limit exceeded",
"status": 429,
"detail": "You have reached your monthly DPP quota (products and passports). Contact your administrator to increase your limit."
}

In addition to the catalog above, the API can return generic HTTP errors that are not Keyban-specific:

  • 400 Bad Request — Zod validation failure on a request body or query parameter. The detail field carries the validation message.
  • 401 Unauthorized — missing or invalid Authorization / x-api-key.
  • 403 Forbidden — authenticated request lacks the required organization permission.
  • 404 Not Found — referenced resource (passport, certification key, agent wallet, …) does not exist or is not visible to the current organization.
  • 409 Conflict — resource state forbids the operation (e.g. wallet still deploying when adding a signer, passport already exists for the given identifier).

These responses still follow the Problem Details envelope but use type: "about:blank" and a generic title.

Client-side errors (SdkErrorCode)

Thrown by the SDK as instances of SdkError. The code field carries the discriminator value.

#SdkErrorCodecode valueDefault message
1AddressInvalidADDRESS_INVALIDAddress is invalid
2AmountInvalidAMOUNT_INVALIDThe specified amount is invalid
3AmountIrrelevantAMOUNT_IRRELEVANTThe amount provided is irrelevant and should not be included
4AmountRequiredAMOUNT_REQUIREDAn amount is required for this operation
5EstimateGasExecutionESTIMATE_GAS_EXECUTIONGas estimation failed
6InsufficientFundsINSUFFICIENT_FUNDSInsufficient funds to complete the transaction
7InvalidNftStandardINVALID_NFT_STANDARDInvalid NFT standard. Supported standards are ERC721 and ERC1155
8NftNotFoundNFT_NOT_FOUNDNFT not found with the provided contract address and token ID
9RecipientAddressEqualsSenderRECIPIENT_ADDRESS_EQUALS_SENDERRecipient address cannot be the same as the sender address
10RelayFailedRelayFailedRelay transaction failed
11TokenBalanceNotFoundTOKEN_BALANCE_NOT_FOUNDToken balance not found with the provided contract address
12UnexpectedUNEXPECTEDAn unexpected runtime invariant was violated
13UnknownIframeApiErrorUNKNOWN_IFRAME_API_ERRORAn unknown error occured with iframe API call
14UnknownTransactionErrorUNKNOWN_TRANSACTION_ERRORAn unknown error occurred with the transaction

SdkErrorCode.AddressInvalid

  • code value: ADDRESS_INVALID
  • Default message: Address is invalid
  • When raised: The Ethereum address does not conform to the expected format.
  • Possible cause: Typographical errors in the address or incorrect formatting.

SdkErrorCode.AmountInvalid

  • code value: AMOUNT_INVALID
  • Default message: The specified amount is invalid
  • When raised: The amount specified for a transaction is not acceptable.
  • Possible cause: Amount is zero, negative, or exceeds allowed limits.

SdkErrorCode.AmountIrrelevant

  • code value: AMOUNT_IRRELEVANT
  • Default message: The amount provided is irrelevant and should not be included
  • When raised: An operation does not require an amount, but one was supplied.
  • Possible cause: Misuse of the API by providing unnecessary parameters.

SdkErrorCode.AmountRequired

  • code value: AMOUNT_REQUIRED
  • Default message: An amount is required for this operation
  • When raised: An operation that requires a monetary amount did not receive one.
  • Possible cause: Missing amount parameter in the function call.

SdkErrorCode.EstimateGasExecution

  • code value: ESTIMATE_GAS_EXECUTION
  • Default message: Gas estimation failed
  • When raised: The SDK was unable to estimate the gas required for a transaction.
  • Possible cause: Network issues, incorrect transaction parameters, or contract errors.

SdkErrorCode.InsufficientFunds

  • code value: INSUFFICIENT_FUNDS
  • Default message: Insufficient funds to complete the transaction
  • When raised: The user's account balance is too low to cover the transaction amount and associated fees.
  • Possible cause: Attempting a transaction with an amount exceeding the available balance.

SdkErrorCode.InvalidNftStandard

  • code value: INVALID_NFT_STANDARD
  • Default message: Invalid NFT standard. Supported standards are ERC721 and ERC1155
  • When raised: An unsupported NFT standard was specified.
  • Possible cause: Using an NFT standard other than ERC721 or ERC1155.

SdkErrorCode.NftNotFound

  • code value: NFT_NOT_FOUND
  • Default message: NFT not found with the provided contract address and token ID
  • When raised: The NFT identified by the provided contract address and token ID does not exist.
  • Possible cause: Incorrect token ID, wrong contract address, or the NFT has been burned.

SdkErrorCode.RecipientAddressEqualsSender

  • code value: RECIPIENT_ADDRESS_EQUALS_SENDER
  • Default message: Recipient address cannot be the same as the sender address
  • When raised: A transaction was attempted where the sender and recipient are identical.
  • Possible cause: User error in specifying addresses or attempting redundant transactions.

SdkErrorCode.RelayFailed

  • code value: RelayFailed
  • Default message: Relay transaction failed
  • When raised: The backend /v1/signers/ecdsa-schnorr/relay endpoint returned a non-2xx status. The rootError carries the message returned by the API, which usually includes the on-chain revert reason (e.g. an OZ custom error selector or an executeRelayed revert message).
  • Possible cause: Operator pool exhausted, RPC error, on-chain revert (insufficient funds, invalid relay signature, account not deployed, etc.), or a request timeout.

SdkErrorCode.TokenBalanceNotFound

  • code value: TOKEN_BALANCE_NOT_FOUND
  • Default message: Token balance not found with the provided contract address
  • When raised: The token balance identified by the provided contract address does not exist.
  • Possible cause: Incorrect wallet address, wrong contract address.

SdkErrorCode.Unexpected

  • code value: UNEXPECTED
  • Default message: An unexpected runtime invariant was violated
  • When raised: A value expected to be non-null was null or undefined.
  • Possible cause: Backend returned a payload inconsistent with its schema, or an assumption about data shape no longer holds.

SdkErrorCode.UnknownIframeApiError

  • code value: UNKNOWN_IFRAME_API_ERROR
  • Default message: An unknown error occured with iframe API call
  • When raised: An unknown error during iframe API call.

SdkErrorCode.UnknownTransactionError

  • code value: UNKNOWN_TRANSACTION_ERROR
  • Default message: An unknown error occurred with the transaction
  • When raised: An unknown transaction error.