Skip to main content

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 by POST /billing/upgrade/checkout.
  • LOYALTY_CAMPAIGN_NOT_RESUMABLE (409): a resume was attempted on a campaign that is not stuck in sending (wrong status, or its send job is still in progress) — only a stranded sending campaign (failed/absent job) can resume.
  • LOYALTY_CAMPAIGN_NOT_SCHEDULABLE (409): a schedule was attempted on a campaign that is not a draft — 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 not scheduled — only a scheduled send can be cancelled back to draft.
  • 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): a conflict extraction 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 and active.
    • 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 (consumedAt null) 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 no admin or owner of 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 no active signer 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 than relayHash (an EIP-191 digest, typically).
    • TREASURY_DISABLED (403): launch toggle FEATURE_TREASURY is 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, the detail names 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's etat_administratif is C (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 match FRxx999999999.
  • 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 the FEATURE_ONBOARDING_V3 launch toggle and is off (ADR 0090).