TypeAlias.ApiErrorCode
type ApiErrorCode = ObjectEnum<typeof ApiErrorCode>;
Stable, versioned error codes carried by RFC 7807 responses (problem-details.ts,
field code). The code never changes once shipped; clients map it to a localized
message via their own catalog and fall back to the English detail when unknown.
Only errors that can reach a browser UI (admin, dpp consumer) carry a code —
backend-only failures (5xx, device callbacks) leave it undefined.
Codes are grouped by domain. Groups added for the email/error i18n effort (#4222):
QUOTA_*: organization quota limits and restrictions.QUOTA_{APPLICATION,DPP,WALLET,AGENT_WALLET}_LIMIT_REACHED(429): the monthly/total quota for that subject is exhausted — the admin must raise the plan limit.QUOTA_FEATURE_RESTRICTED/QUOTA_NETWORK_RESTRICTED(403): the requested feature or blockchain network is not enabled for the organization's plan.
BILLING_CHARGEBEE_ITEM_NOT_FOUND(422): the resolved Chargebee item_price has no linked item_id (misconfigured catalog); surfaced during onboarding/plan sync.BILLING_UPGRADE_NOT_ALLOWED(422): the requested item price is not a self-serve upgrade of the organization's current plan — a different vertical, a same-or-lower tier, or a target that is Enterprise or a hidden free plan. Surfaced byPOST /billing/upgrade/checkout.LOYALTY_CAMPAIGN_NOT_RESUMABLE(409): a resume was attempted on a campaign that is not stuck insending(wrong status, or its send job is still in progress) — only a strandedsendingcampaign (failed/absent job) can resume.LOYALTY_CAMPAIGN_NOT_SCHEDULABLE(409): a schedule was attempted on a campaign that is not adraft— only a draft can be scheduled for a future send.LOYALTY_CAMPAIGN_NOT_CANCELLABLE(409): a schedule cancellation was attempted on a campaign that is notscheduled— only a scheduled send can be cancelled back todraft.WEBHOOK_*: outbound webhook endpoint management.WEBHOOK_ENDPOINT_NOT_FOUND(404): the endpoint id does not exist for the caller's organization.WEBHOOK_URL_INVALID(400): the target URL is not a valid absolute HTTPS URL.WEBHOOK_URL_BLOCKED(400): the URL resolves to a private, loopback, link-local or cloud-metadata address and is refused (SSRF protection).
AGENT_WALLET_EXPENSE_EXPORT_TOO_LARGE(400): the CSV export date range + filters match more transactions than the export cap — narrow the range.IMPORT_RUN_IN_PROGRESS(409): a manual run was requested on a mapper whose previous run is still importing. Runs are refused rather than overlapped: two passes over the same source race each other on the passport identity index.DPP_STRUCTURE_JOB_NOT_FOUND(404): the structuration job id does not exist (or its progress stream was requested after the underlying queue job expired).DPP_CHARACTERISTIC_REVIEW_NOT_FOUND(404): the characteristic has no AI extraction to review on this passport (already reviewed elsewhere, or source data).DPP_CHARACTERISTIC_CONFLICT_NOT_APPROVABLE(409): aconflictextraction cannot be approved — the existing value always stays; edit the passport manually or reject the extraction instead.MULTISIG_*: organization multisig wallet (admin treasury, M-of-N app signers).MULTISIG_WALLET_SESSION_REQUIRED(403): creation/enrollment requires a session-authenticated user (API keys can read but not mutate).MULTISIG_WALLET_ALREADY_EXISTS(409): the organization already has a multisig wallet on the requested network (one per organization × network).MULTISIG_WALLET_ALREADY_DEPLOYED(409): deletion refused because the wallet is (being) deployed on-chain; only a never-deployed wallet can be removed.MULTISIG_WALLET_NOT_ACTIVE(409): the operation (signer enrollment, topup) requires the wallet to be deployed andactive.MULTISIG_SIGNER_ALREADY_ENROLLED(409): the user already has a live signer enrollment on this wallet, or the address is already bound to it.MULTISIG_SIGNER_NOT_INVITED(403): nobody asked this member to hold a key on this treasury — enrolment is reserved to invitees, so a live invitation (consumedAtnull) is required. The treasury creator is auto-invited at creation; anyone else needs an invitation from a peer. Independent of RBAC: being an org admin is necessary but no longer sufficient.MULTISIG_SIGNER_NOT_VALIDATOR(403): the caller holds no validated or active signer row on the wallet, so cannot vouch for an enrollment.MULTISIG_SIGNER_NOT_REQUESTED(409): the target enrollment is not awaiting a validation (already validated, active, or failed).MULTISIG_SIGNER_NOT_CHALLENGED(409): the SAS ceremony is not ready — no challenge yet, the candidate has not revealed its nonce, or the challenge window elapsed and a fresh challenge is needed.MULTISIG_SIGNER_COMMITMENT_INVALID(422): the revealed nonce does not reproduce the enrollment commitment — a client bug or a tampered reveal.MULTISIG_SIGNER_SAS_MISMATCH(422): the typed 4-digit code does not match the one derived from the enrolled signer and the two nonces. The enrollment is burned (failed): the short code allows a single online guess, so a wrong one forecloses re-grinding rather than inviting another try.MULTISIG_SIGNER_PUBKEY_INVALID(422): the submitted passkey public key is not a valid uncompressed P-256 SubjectPublicKeyInfo, or the point is not on the curve — nothing was enrolled.MULTISIG_WEBAUTHN_UNAVAILABLE(409): the treasury's network has no deployed ERC-7913 WebAuthn verifier yet (deployments.json is the SSOT), so passkey signers cannot be composed there; enroll an EOA key instead.MULTISIG_WALLET_SELF_ADDRESS(422): the submitted signer address is the treasury itself, which would make signature validation recurse.MULTISIG_INVITATION_NOT_A_MEMBER(400): the invited email matches noadminorownerof the organization. Enrolling is the invitee's own authenticated call, so anyone else would be handed an invitation they could never complete — invite them to the organization first.MULTISIG_INVITATION_ALREADY_EXISTS(409): that member already has a pending invitation on this treasury, or is already one of its signers. Only raised by the unitary invite; a batch skips such members silently.MULTISIG_TOPUP_TARGET_INVALID(422): the topup destination is not an agent wallet of the organization on the same network, or has no on-chain address.MULTISIG_TOPUP_ASSET_UNSUPPORTED(409): no canonical USDC contract is known for the treasury's network, so no transfer can be built.MULTISIG_TOPUP_INSUFFICIENT_FUNDS(422): the treasury's on-chain USDC balance cannot cover the lot's total at creation time. Best-effort guard: an unreadable balance skips it, and the balance can still move before execution, where the atomic batch reverts as a whole.MULTISIG_BATCH_TOO_LARGE(422): the lot exceeds 20 legs — one signature covers the whole lot, but the phone screen and the gas of an atomic batch stay bounded. Split into several lots. Defense in depth for internal callers: over HTTP the DTO's own bound rejects the lot as a 400 first.MULTISIG_INTENT_ALREADY_PENDING(409): the treasury already has a transfer awaiting signatures, or one whose outcome is still undetermined. Signatures are bound to a single account nonce, so intents cannot overlap.MULTISIG_INTENT_NOT_PENDING(409): the intent no longer collects signatures (broadcasting, settled, cancelled or expired), so it can be neither approved nor cancelled.MULTISIG_INTENT_NOT_SIGNER(403): the caller holds noactivesigner row on this treasury — enrolled or merely validated is not enough, the key must be in the on-chain signer set.MULTISIG_INTENT_ALREADY_APPROVED(409): this signer already signed the intent; a second signature would not count towards the threshold.MULTISIG_INTENT_SIGNATURE_INVALID(422): the signature does not recover to the caller's enrolled address — wrong key, or a signature over something other thanrelayHash(an EIP-191 digest, typically).TREASURY_DISABLED(403): launch toggleFEATURE_TREASURYis off on this environment (ADR 0090) — every treasury route is refused regardless of RBAC.
PUSH_*: Expo push notification device tokens.PUSH_SESSION_REQUIRED(403): registering or removing a device token requires a session-authenticated user — a token belongs to a person's phone, so an API key has no device to speak for.
ONRAMP_*(card top-up → USDC on Base, pilot):ONRAMP_KYC_REQUIRED(403): Stripe requires L2 document verification before a session can be created.ONRAMP_LINK_AUTH_REQUIRED(401): no valid Link OAuth token for this user — redo the OTP flow.ONRAMP_LINK_ACCOUNT_NOT_FOUND(404): no Stripe Link account exists for the e-mail submitted to start the auth intent — Link does NOT create a consumer implicitly (empirical, 2026-08-19); the operator must fix the address.ONRAMP_SESSION_LOCK_FAILED(502): Stripe returned a session without the requested destination locks — the session was abandoned, no payment is possible.ONRAMP_WALLET_NOT_FUNDABLE(422): the target destination — agent wallet or organization treasury — cannot be topped up (no on-chain address, a network other than Base mainnet, or a status that cannot receive funds). Shared by both destination kinds on purpose: the refusal reasons are identical, thedetailnames which one was refused and why.ONRAMP_CLIENT_IP_UNRESOLVED(422): the client's public IP could not be resolved, which Stripe's geo-check requires.ONRAMP_DESTINATION_INVALID(422): the session request names both a wallet and a treasury, or neither — a funding has exactly one destination, and a session must be locked on the one address the money will land at.
LEGAL_ACCEPTANCE_REQUIRED(422): a required legal document (CGU, CGV) is missing or accepted at a version older than the current one for the caller's audience — onboarding cannot proceed until it is (re-)accepted.ONBOARDING_COMPANY_*(company lookup by SIREN, public « Recherche d'entreprises » registry):ONBOARDING_COMPANY_NOT_FOUND(404): no company matches the given SIREN.ONBOARDING_COMPANY_LOOKUP_UNAVAILABLE(503): the registry could not be reached (network error, timeout, or non-2xx response).ONBOARDING_COMPANY_CLOSED(422): the matched company'setat_administratifisC(ceased) — onboarding is refused for it.ONBOARDING_SIREN_REQUIRED(422): the billing identity is a company domiciled in France without a SIREN.ONBOARDING_VAT_REQUIRED(422): the billing identity is a company domiciled outside France without a VAT number.ONBOARDING_LEGAL_NAME_REQUIRED(422): the billing identity is a company without a legal name.ONBOARDING_INDIVIDUAL_HAS_COMPANY_ID(422): the billing identity is an individual carrying a SIREN or VAT number.ONBOARDING_VAT_INVALID(422): the billing identity is domiciled in France and its VAT number does not matchFRxx999999999.
ONBOARDING_PLAN_NOT_FOUND(404): the requested plan price id matches no self-service plan — unknown, retired, or an Enterprise plan (which goes through the contact-sales funnel).ONBOARDING_AUDIENCE_NOT_ALLOWED(403): the plan is restricted to an audience the caller's billing identity does not belong to (a company-only plan requested by an individual).ONBOARDING_V3_DISABLED(403): the self-service onboarding v3 tunnel is behind theFEATURE_ONBOARDING_V3launch toggle and is off (ADR 0090).