Skip to main content

Error code reference

Every non-2xx response uses the canonical error envelope; branch on code, never on message. This page covers the codes introduced by the identity/credential, authority/approvals, unified-payments, and webhooks surfaces. Statuses shown are the code's canonical status.

Verification sessions & handoff

codeStatusMeaning
VERIFICATION_SESSION_NOT_FOUND404No session with that id in your tenant.
VERIFICATION_SESSION_TERMINAL409Session already reached a terminal state (VERIFIED/REJECTED/EXPIRED/CANCELED/ERROR).
VERIFICATION_SESSION_NOT_STARTED409No provider session exists to advance.
SIMULATE_NOT_AVAILABLE403/simulate needs a TEST key + the mock provider — LIVE sessions are never advanceable by API call.
MEMBER_ID_REQUIRED400KYC sessions need memberId.
MEMBER_NOT_FOUND404No such member in your tenant.
MEMBER_NOT_ACTIVE409Only ACTIVE members can be verified / hold authority.
LEGAL_NAME_REQUIRED400No legal name available for the subject.
HANDOFF_GONE410The one-time QR link was already consumed or expired — mint a new one.

Credentials

codeStatusMeaning
CREDENTIAL_NOT_FOUND404No credential with that id in your tenant.
CREDENTIAL_NOT_REFRESHABLE404The credential has no provider linkage to re-read.
CREDENTIAL_ARTIFACT_NOT_AVAILABLE404Legacy unsigned row — re-issuance supersedes it; there is no artifact to fetch.
UNKNOWN_ELIGIBILITY_ACTION400The eligibility endpoint only evaluates known gate actions.

Identities & wallet bindings

codeStatusMeaning
IDENTITY_NOT_FOUND404No identity with that id (or DID) in your tenant.
TENANT_NOT_FOUND404TENANT-subject identity requests are fixed to your own tenant.
INVALID_WALLET_ADDRESS400Not a valid EVM address.
WALLET_BINDING_NOT_FOUND404No such binding under that identity.
WALLET_BINDING_NOT_PENDING409Binding is not PENDING (already verified, revoked, or concurrently consumed).
WALLET_BINDING_NO_CHALLENGE409Binding has no outstanding challenge.
WALLET_BINDING_SIGNATURE_INVALID400Signature does not recover to the wallet being bound (sign the exact challenge text with EIP-191 personal_sign).
WALLET_BINDING_NOT_ACTIVE409Only ACTIVE bindings can be rotated.
WALLET_BINDING_NOT_ROTATING409The binding is not an open rotation.
WALLET_BINDING_ROTATION_STALE409The binding being replaced stopped being ACTIVE mid-rotation — start over.

Credential presentations

codeStatusMeaning
CHALLENGE_NOT_FOUND404Unknown nonce (or another tenant's challenge).
PRESENTATION_REPLAYED409The challenge was already consumed — presentations are single-use, and a failed verify burns its challenge too.

Verification failures are NOT errors: POST …/verify returns 200 with verified: false and reasonCodes (CHALLENGE_EXPIRED, HOLDER_UNKNOWN, HOLDER_MISMATCH, SIGNER_NOT_BOUND, PRESENTATION_SIGNATURE_INVALID, CREDENTIAL_INVALID) plus per-credential verdicts.

Authority & payment policy

codeStatusMeaning
AUTHORITY_NOT_FOUND404No authority with that id in your tenant.
AUTHORITY_ACTIONS_REQUIRED400allowedActions must name at least one gate action.
INVALID_USD_AMOUNT400Limits must be positive USD amounts (≤ 6 fraction digits).
INVALID_APPROVAL_WEIGHT400approvalWeight must be ≥ 1.
AUTHORITY_REVOKED409A revoked authority is immutable — grant a new one.
AUTHORITY_NOT_ACTIVE409Only ACTIVE authorities can be attested.
AUTHORITY_ALREADY_ATTESTED409The authority already carries its credential.
ORG_SAFE_NOT_DEPLOYED409Org-signed credentials require a DEPLOYED tenant Safe (EIP-1271); no fallback signer exists.
ORG_ATTESTATION_INVALID400The Safe signature does not validate on-chain (threshold not met or wrong hash).
PAYMENT_POLICY_NOT_FOUND404No policy version to attest.
PAYMENT_POLICY_ALREADY_ATTESTED400The active policy version is already attested.
PAYMENT_POLICY_UNKNOWN_KEY400Unknown rule key in the policy document.
INVALID_POLICY_RULE400A rule value failed validation.

Approvals

codeStatusMeaning
APPROVAL_NOT_FOUND404No approval request with that id in your tenant.
APPROVAL_NOT_PENDING409 (400 on /payments/{id}/approve when none exists)The request is not open for votes / the payment has no pending request.
APPROVAL_REQUIRES_MEMBER403Votes come from members, not applications — an API key cannot approve.
SELF_APPROVAL_FORBIDDEN403The member who initiated a payment can never approve it (segregation of duties).
AUTHORITY_MISSING403No ACTIVE authority grants payment.approve (also a gate hold reason on payments).
APPROVAL_GROUP_MISMATCH403The request is scoped to a different approval group.
ALREADY_VOTED409One vote per member per request.
APPROVAL_SIGNATURE_INVALID400The optional vote signature does not recover to the member's signer address.
NO_SIGNER_ADDRESS400The member has no registered signer address to verify a signature against.

Unified payments (/v1/payments)

codeStatusMeaning
AMBIGUOUS_PAYMENT_BODY400Body carries both recipient and sourceWalletId — send one generation, not both.
UNRECOGNIZED_PAYMENT_BODY400Neither recipient (intent) nor sourceWalletId (legacy) present.
INVALID_PAYMENT_BODY400The normalized intent failed validation (details lists issues); it never falls through to the legacy path.
RECIPIENT_REQUIRED400An intent needs a resolvable recipient.
RECIPIENT_AMBIGUOUS400The recipient object matched more than one resolution key — send exactly one of did / phoneNumber / email / identityId.
RECIPIENT_NOT_ROUTABLE400The recipient exists but cannot receive routed payments.
PAYEE_NOT_FOUND / EMAIL_NOT_FOUND / IDENTITY_NOT_FOUND404The recipient reference did not resolve. A Permara ID nobody holds is never reserved by guessing — invite by email or mobile number instead.
INVALID_DID400Not a did:permara: identifier (64 lowercase hex characters after the prefix).
DID_NOT_RESOLVABLE400A DID of another method; only did:permara resolves here.
INVITE_CONTACT_REQUIRED400Reserving an identity needs an email or a phone.
UNSUPPORTED_RECIPIENT_KIND400The recipient kind is not supported on the intent path.
INVALID_AMOUNT400Not a valid USD amount.
INVALID_PHONE400Not a valid E.164 phone number.
FUNDING_MODE_NOT_AVAILABLE400The requested funding mode is not available for the tenant.
DESTINATION_NOT_ELIGIBLE403Client-supplied destination is not owned + ACTIVE for the resolved recipient.
PAYMENT_NOT_FOUND404Unknown payment id (across both generations).
NOT_SENDER403Only the sending tenant may perform this action.
RECIPIENT_CONSENT_REQUIRED403Route selection/confirmation belongs to the recipient.
ROUTE_NOT_SELECTED409Confirm requires a selected route quote.
ROUTE_QUOTE_EXPIRED409The selected quote expired — re-fetch /routes and re-select.
ROUTE_QUOTE_NOT_AVAILABLE404The quote no longer exists.
RECIPIENT_UNRESOLVED409Routing needs a resolved recipient (still in claim/onboarding).
CANCEL_CONFLICT409The payment is past the point of cancellation.
INVALID_TRANSITION400The requested action is illegal from the payment's current state.
IDEMPOTENCY_KEY_REUSED409The idempotency key was reused with a different payment body (salient-field hash mismatch). The global interceptor also rejects raw body-hash mismatches with 422.
LIQUIDITY_INSUFFICIENT / LIQUIDITY_NOT_CONFIGURED / LIQUIDITY_BAD_AMOUNT409 / 404 / 409Provider prefunded-liquidity constraints.
REVIEW_CASE_NOT_FOUND / REVIEW_CASE_NOT_OPEN / REVIEW_CASE_RESOLVED404 / 409 / 409Admin review-case lifecycle.

Balance & funding (/v1/balance, /v1/faucet/drips)

CodeHTTPMeaning
INSUFFICIENT_FUNDS402The payer's primary account holds less than the payment needs. details.shortfallBaseUnits says how much; details.funding says how money gets in (FAUCET on a testnet).
ACCOUNT_NOT_READY409No account holds the payer's money yet (details.status). GET /v1/balance?ensure=true provisions it.
ACCOUNT_CHAIN_MISMATCH400The payment names a chain the payer's account is not on.
BALANCE_UNAVAILABLE503The balance could not be read; nothing is assumed available. Retry.
SMART_ACCOUNT_NOT_LINKED409The member has no smart account yet. Sign in to the app once (Privy links it), or GET /v1/balance?ensure=true.
ORG_ACCOUNT_NOT_READY409The organization account is missing or not deployed yet; details.status says which. GET /v1/balance?ensure=true provisions it.
FAUCET_NOT_AVAILABLE403Demo USD is minted on testnets only; the account's chain is a mainnet.
FAUCET_RATE_LIMITED403The funded address (20/day) or the tenant (100/day) has used its drips.
AMOUNT_EXCEEDS_CAP400More than 1000 Demo USD (or 0.02 ETH) in one drip.

Payout destinations

codeStatusMeaning
DESTINATION_NOT_FOUND404No destination with that id.
DESTINATION_EXISTS409An equivalent destination already exists.
DESTINATION_REVOKED409Revoked destinations never mutate back — create a new one.
DESTINATION_NOT_VERIFIED / DESTINATION_NOT_ACTIVE409Status is enforced at the read path — the router only accepts ACTIVE.
DESTINATION_VERIFY_NOT_AVAILABLE403No verification flow available for this rail.
DESTINATION_TYPE_REQUIRED / DESTINATION_IDENTIFIER_REQUIRED / DESTINATION_RAIL_CONSTRAINT / DESTINATION_RAIL_NOT_AVAILABLE400Rail-specific input validation.
UNSUPPORTED_NETWORK / INVALID_DESTINATION_ADDRESS400On-chain destination validation.
IDENTITY_REQUIRED403The identity gate blocked the action (details.missing lists credential kinds).

Webhooks (outbound)

codeStatusMeaning
INVALID_URL400Not a valid absolute URL.
SCHEME_NOT_ALLOWED400LIVE endpoints must be https; TEST may use http.
PRIVATE_HOST_BLOCKED400The host is not a public address (loopback/RFC1918/link-local/metadata/CGNAT are refused — SSRF guard, not overridable).
PORT_NOT_ALLOWED400LIVE endpoints must target port 443.
ENDPOINT_NOT_FOUND404No endpoint with that id in your tenant.
DELIVERY_NOT_FOUND404Unknown delivery id.

Delivery attempts fail with a coarse lastError enum on the delivery row (TIMEOUT, CONNECTION_FAILED, BLOCKED_URL, HTTP_4XX, HTTP_5XX, HTTP_ERROR, SECRET_ERROR, LEASE_EXPIRED, ENDPOINT_GONE) — see Webhooks. Inbound provider webhooks reject with WEBHOOK_SIGNATURE_INVALID (401), WEBHOOK_REPLAY_WINDOW (401), EMPTY_BODY (400), UNKNOWN_PROVIDER (400).

API-key hardening

codeStatusMeaning
API_KEY_REQUIRED / API_KEY_INVALID / API_KEY_REVOKED / API_KEY_EXPIRED401Key missing, malformed/unknown/environment-mismatched, revoked, or expired.
AGENT_SUSPENDED401The agent behind this key is suspended (checked on every request, cache included).
IP_NOT_ALLOWED403The key has an IP allowlist and the request's source IP is not on it.
REQUEST_SIGNATURE_REQUIRED403This key requires signed requests on high-risk routes (x-safebank-request-signature: t=<unix>,v1=<hmac>).
REQUEST_SIGNATURE_STALE403Signature timestamp outside the 300 s tolerance.
REQUEST_SIGNATURE_INVALID403HMAC over timestamp.METHOD.path.sha256(rawBody) does not verify.
REQUEST_SIGNING_NOT_CONFIGURED403Signing is required but no signing secret is set for the key.