# Error codes

> Stable identifier strings returned by the verifier and the settler.

Both the verifier (`/verify`) and the settler (`/settle`) return string error identifiers that merchants and SDKs can match on programmatically. The codes are stable; changing one is a breaking change.

`(V)` indicates the code may be returned by `/verify`. `(S)` indicates `/settle`. `(V, S)` indicates both.

## Verification + settlement codes

- **`invalid_envelope`** *(V)* — `paymentPayload` does not match the V2 envelope schema.
- **`unsupported_scheme`** *(V, S)* — the facilitator does not handle this scheme.
- **`requirements_mismatch`** *(V, S)* — the `accepted` block does not match the merchant's `paymentRequirements`.
- **`network_mismatch`** *(V)* — `accepted.network` differs from the merchant's requirement.
- **`amount_mismatch`** *(V)* — `accepted.amount` differs from the merchant's requirement.
- **`asset_mismatch`** *(V)* — `accepted.asset` differs from the merchant's requirement.
- **`payTo_mismatch`** *(V)* — `accepted.payTo` differs from the merchant's requirement.
- **`invalid_inner_payload`** *(V)* — `payload.userOp` or `payload.settlementCallSpec` is malformed.
- **`calldata_decode_failed`** *(V)* — `userOp.callData` does not decode to `execute(...)` or to an `executeBatch(...)` containing a `settle(...)` invocation.
- **`spec_mismatch`** *(V)* — the decoded `settle` arguments differ from `payload.settlementCallSpec`.
- **`replay`** *(V, S)* — the `requestHash` has already been settled.

## Settlement-only codes

- **`in_flight`** *(S)* — another settlement for this `requestHash` is in progress. Caller should retry after the indicated `Retry-After` interval.
- **`bundler_error`** *(S)* — the bundler rejected the UserOp. Causes include rate-limit rejection, simulation revert, and RPC error.
- **`user_op_failed`** *(S)* — the UserOp executed on chain but reverted. The `message` field carries the surfaced revert reason.
- **`receipt_timeout`** *(S)* — the bundler did not return a receipt within the configured deadline. The UserOp may still settle later; idempotency on `requestHash` ensures a retry will not double-spend.

## Internal

- **`internal_error`** *(V, S)* — facilitator-side fault. The client SHOULD retry once before treating this as a hard failure.

## Reserved

Codes outside this list are reserved. New scheme implementations and new failure modes SHOULD allocate a new code rather than overload an existing one.

## Where the codes appear

For `/verify`: `{isValid: false, invalidReason: "...", details?: ...}`. The `details` field, when present, carries machine-readable specifics (a Zod issue list for envelope errors, a sub-mismatch identifier for `spec_mismatch`).

For `/settle`: `{success: false, x402Version: 2, errorCode: "...", message: "..."}`. The `message` field is human-readable; do not match programmatically on it.

## Programmatic matching example

```ts
const settle = await fetch(facilitatorUrl + "/settle", { ... }).then(r => r.json());

if (settle.success) {
  // happy path
} else {
  switch (settle.errorCode) {
    case "in_flight":
      return retryAfter(...);
    case "user_op_failed":
      return surfaceRevert(settle.message);
    case "bundler_error":
    case "receipt_timeout":
      return retryWithBackoff(...);
    case "replay":
      // already settled — no further action; record the original txHash if possible
      break;
    default:
      // unknown codes are reserved and may be added in future versions
      log.warn("unknown settle errorCode", settle.errorCode, settle.message);
  }
}
```
