> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Permissions errors

> Error codes, denial reasons and policy validation codes that Permissions returns, with what each means and how to handle it.

export const Card = ({imgUrl, compactImgUrl, title, description, href, horizontal = false, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  const handleClick = e => {
    e.preventDefault();
    if (newTab) {
      window.open(href, '_blank', 'noopener,noreferrer');
    } else {
      window.location.href = href;
    }
  };
  return <div className={`not-prose relative my-2 p-[1px] rounded-xl transition-all duration-300 ${isHovered && href ? 'bg-gradient-to-r from-[#FF4E00] to-[#874AE3]' : 'bg-gray-200'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <a href={href} onClick={href ? handleClick : undefined} className={`not-prose flex ${horizontal ? 'flex-row' : 'flex-col'} font-normal h-full bg-white overflow-hidden w-full ${href ? 'cursor-pointer' : 'cursor-default'} rounded-[11px] no-underline`}>
        {imgUrl && <div className={`relative overflow-hidden flex-shrink-0 ${horizontal ? 'w-[30%] rounded-l-[11px]' : 'w-full'}`} onClick={e => e.stopPropagation()}>
            <picture style={{
    display: 'contents'
  }}>
              {compactImgUrl && <source media="(max-width: 639px)" srcSet={compactImgUrl} />}
              <img src={imgUrl} alt={title} className="w-full h-full object-cover pointer-events-none select-none" draggable="false" />
            </picture>
            <div className="absolute inset-0 pointer-events-none" />
          </div>}
        <div className={`flex-grow px-6 py-5 ${horizontal ? 'w-[70%]' : 'w-full'} flex flex-col ${horizontal && imgUrl ? 'justify-center' : 'justify-start'}`}>
          {title && <h2 className="font-semibold text-base text-gray-800 m-0">{title}</h2>}
          {description && <div className={`font-normal text-gray-500 re leading-6 ${horizontal || !imgUrl ? 'mt-0' : 'mt-1'}`}>
              <p className="m-0 text-xs">{description}</p>
            </div>}
        </div>
      </a>
    </div>;
};

Look up an error that Permissions returned while you signed, handled an approval, changed approval settings, read policies or published a policy. Each table gives the code, the HTTP status, the server message, what the error means and what to do next.

## How errors reach your code

The server attaches a code to every Permissions error, but not every client receives it. The REST API and the REST SDK always return the code. The JavaScript SDKs receive only a few codes, listed in the Warning below, and get most Permissions errors as plain text without one. Find your client here, then look up the code in the sections below.

| Client | Request header | Error you receive | Code | Extra fields |
| - | - | - | - | - |
| REST API | None needed | JSON body | `code` | Top level of the body, such as `deniedBy` or `approvalCaseId` |
| REST SDK | None needed | `ParaRestError` | `error.code`, with `error.status` and `error.requestId` | `error.body`, the full parsed body |
| JavaScript SDKs | `x-para-structured-errors: 1`, sent automatically | An error object, not always an `Error` instance | `error.code`, with `error.status` | `error.data`, such as `error.data.deniedBy` |
| CLI | `x-para-client-type: cli`, sent automatically | Exit code and message | `errorCode` in `--json` output | `details`, such as `details.errors` |

<Warning>
  On the routes the JavaScript SDKs call, `POLICY_DENIED`, `SPEND_ACCOUNTING_FAILED`, `INITIATOR_CANNOT_REVIEW` and `PENDING_TRANSACTION_ALREADY_APPROVED` arrive as JSON with a code. So do `POLICY_REVIEW_REQUIRED` from SDK signing, `user is not linked to this app` when you sign with an app-owned wallet, `APPROVAL_CASES_AFFECTED` when a configuration change would invalidate open approval cases, and the [decision refusals](#decision-refusals) when an approve or reject is refused. Other Permissions errors on these routes arrive as plain text. For those, `error.message` holds the server message listed below, `error.code` is `ERR_BAD_REQUEST` for a 4xx status or `ERR_BAD_RESPONSE` for a 5xx status, and `error.data` is `undefined`. Match them on `error.status` and `error.message`.
</Warning>

<Note>
  The JavaScript SDKs export no class for API errors. SDK calls reject with an error object that carries `code` and `status`, plus `data` when the server returned JSON. The object is not always an `Error` instance, so check its `code` property rather than its class. Signing calls are the exception in the web and React SDKs: when the server refuses one, for example with a policy denial, they currently reject with a generic error that has no `code`. The server SDK and the React Native SDK keep the `code`, `status` and `data`.
</Note>

The REST and SDK tabs show a policy denial. The CLI does not sign, so its tab shows a policy create that conflicts with an existing draft.

<CodeGroup>
  ```json REST API theme={null}
  {
    "code": "POLICY_DENIED",
    "message": "Transaction denied by policy",
    "deniedBy": [
      {
        "policyId": "app-guardrails",
        "ruleId": "deny-unlisted-contracts",
        "reason": "matched:partner:deny-unlisted-contracts"
      }
    ]
  }
  ```

  ```json React Native and server SDKs theme={null}
  {
    "code": "POLICY_DENIED",
    "status": 403,
    "message": "Transaction denied by policy",
    "data": {
      "deniedBy": [
        {
          "policyId": "app-guardrails",
          "ruleId": "deny-unlisted-contracts",
          "reason": "matched:partner:deny-unlisted-contracts"
        }
      ]
    }
  }
  ```

  ```json CLI --json theme={null}
  {
    "error": {
      "type": "ValidationError",
      "errorCode": "API_VALIDATION_ERROR",
      "exitCode": 10,
      "message": "The Para API rejected the permissions policy request. Check the field errors and try again.",
      "details": {
        "errors": [
          {
            "code": "policy_record_draft_already_exists",
            "path": "$.policy",
            "message": "a draft already exists for this product mode; activate or archive it before creating another"
          }
        ]
      }
    }
  }
  ```
</CodeGroup>

## Signing

These errors come from the REST signing endpoints (`sign-raw`, `sign-transaction`, `sign-message`, `sign-typed-data`, `sign-authorization` and `transfer`) and from SDK signing calls. Para evaluates your policies before it signs, so a policy error means nothing was signed.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `POLICY_DENIED` | 403 | `Transaction denied by policy` | A `deny` rule matched, an approver rejected the request, or Para could not evaluate the request safely, for example because a rule could not read a fact it needs. `deniedBy[0].reason` says which. | Do not resend the same request. Branch on the reason in [Denial reasons](#denial-reasons). |
| `POLICY_DENIED` | 403 | `Transaction denied by policy` | The policy allowed the request, but other spending, usually from concurrent requests, filled a spending limit's window before Para reserved this amount. `deniedBy[0].reason` is `windowed-spend-limit-exceeded`, and `policyId` and `ruleId` name the rule that allowed the request. A request that is already over the limit when Para evaluates it is decided by the limit rule instead. | Wait for the window to reset, or lower the amount. |
| `POLICY_DENIED` | 403 | `Transaction denied by policy` | The policy allowed the request with a completed approval, but another signing call claimed that approval first. `deniedBy[0].reason` is `approval-reservation-unavailable`, and `policyId` and `ruleId` name the approved rule. | Do not resend. A completed approval signs one request once. |
| `POLICY_DENIED` | 403 | `Transaction denied by policy` | An older scope-based policy denied the request. `deniedBy` is an object with `scopeName`, `permissionType` and `condition`. When that policy's spending window is full, it names the spending condition that was reached. It can be absent, for example when no scope allows the request. | Change the request or the scope-based policy. |
| `SPEND_ACCOUNTING_FAILED` | 500 | `Windowed spend accounting failed` | Para could not record the spend against a spending limit, so it did not sign. | Retry with backoff. On REST, reuse the same `Idempotency-Key`. |
| `PERMISSIONS_V2_EXTENSION_UNAVAILABLE` | 403 | `permissions v2 extension is not available for this partner` | The deciding rule has an approval requirement, and conditional approvals are not enabled for your app. When user-adjustable parameters are not enabled, signing is denied with `parameter_configuration_unavailable` instead. | Contact Para to enable them. Retrying does not help. |
| `INVALID_REQUEST` | 400 | `Invalid canonical transaction`, `invalid canonical transaction`, `canonical transaction required for non-EVM permissions v2 evaluation` | Para could not parse the transaction to evaluate it, or a non-EVM request did not include a transaction it can evaluate. | Fix the transaction payload. |
| `INVALID_REQUEST` | 400 | `permissions v2 review approval subject invalid: <detail>` | Para could not derive what the review should approve from the request. | Fix the request as the detail describes. |
| `INVALID_REQUEST` | 400 | `conditional approval owner review is unavailable`, `unsupported or invalid conditional approval transaction signature payload` | The request does not fit the approval requirement that the deciding rule selected. | Check the request against the rule's `approvalRequirement`. |
| `CONFLICT` | 409 | `permissions v2 user parameters changed; retry signing` | The user's adjustable limits changed while Para evaluated the request. | Retry the signing call. |
| `CONFLICT` | 409 | `conditional approval signing dispatch is already claimed or unavailable` | The completed approval for this request is being used to sign right now, or was already used. | Do not resend with the same approval. Start a new request if you still need a signature. |
| `INVALID_REQUEST` | 400 | `user is not linked to this app` | `initiatorUserId` is not a UUID, or that user has no wallet in your app. | Send the ID of a user with a wallet in your app, or omit `initiatorUserId`. |
| `INVALID_REQUEST` | 400 | `partner project context missing` | Your API key's app has no project. | Contact Para. |
| `INVALID_REQUEST` | 400 | `Safe spend subject is invalid` | Para could not read a valid Safe payment from the request to count it against a spending limit. | Check the Safe transaction. Contact Para if it repeats. |
| `SPONSORSHIP_CONDITIONAL_APPROVAL_UNSUPPORTED` | n/a | `Conditional approval policies are not supported for sponsored transfers in this pilot. Contact Para before retrying; existing wallet policies remain enforced.` | A sponsored transfer matched a rule with an approval requirement. The tracked transfer fails with this `failureCode` instead of an HTTP error. | Contact Para before you retry. |

## Review required

When the deciding rule requires approval, a REST signing call returns `403` with `POLICY_REVIEW_REQUIRED` and does not sign. In the server SDK and the React Native SDK, signing with an app-owned wallet throws the same error, with the fields in `error.data`.

The web and React SDKs currently reject with a generic error that has no `data`.

SDK signing with a user's wallet works differently: the server returns a pending transaction, and the SDK sends the user to review and waits, or returns the review URL in `handoff` mode. Those outcomes are listed under [SDK error classes](#sdk-error-classes).

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `POLICY_REVIEW_REQUIRED` | 403 | `Transaction requires policy review` | The deciding rule requires approval. The body carries `approvalCaseId` and `approvalCaseStatus` when the rule has an approval requirement, or `reviewId` otherwise, plus `artifactId` and `reason` (`matched:<layer>:<ruleId>`). | Wait for the approval, then resend the identical request with the same `Idempotency-Key`. Error responses are not replayed, so Para evaluates the resend and signs once approved. |
| `POLICY_REVIEW_REQUIRED` | 403 | `Transaction requires policy review` | Server SDK or React Native signing with an app-owned wallet reached a rule that requires approval. With an `approvalRequirement`, `error.data` carries `approvalCaseId`, `approvalCaseStatus`, `artifactId` and `reason`. Otherwise, it carries `reviewId`, `artifactId` and `reason`, with no approval-case fields. | For an approval case, wait for approval, then sign the identical request again with `resumeApprovalCaseId` set to `approvalCaseId`. While the case is `pending`, the call throws this error again. Once the case is `completed` and its approval is valid and unused, it signs. An ordinary `reviewId` cannot be used as `resumeApprovalCaseId`. Have your backend approve or reject that review with your app's server secret as described in [Pending transaction review](#pending-transaction-review). Retry identical SDK signing input without `resumeApprovalCaseId` only after `approved`, while the approval is unused and unexpired and the request and policy binding still match. |
| `POLICY_REVIEW_REQUIRED` | 403 | `Transaction requires policy review` | An SDK signing call passed `resumeApprovalCaseId` for a case that can no longer authorize signing. `approvalCaseStatus` shows its state, such as `expired`, `invalidated` or `cancelled`, or `completed` when its approval was already used or expired. | Sign again without `resumeApprovalCaseId` to open a new case. For a user's own transaction whose completed approval expired before use, follow the Warning below. |
| `INVALID_REQUEST` | 400 | `invalid resumeApprovalCaseId` | `resumeApprovalCaseId` is not a string of 1 to 64 characters. | Pass the `approvalCaseId` from the earlier error. |
| `INVALID_REQUEST` | 400 | `resumeApprovalCaseId cannot be combined with approvalCaseId` | A message signing call passed both `resumeApprovalCaseId` and the `approvalCaseId` of a signature collection. | Send only one of them. |
| `NOT_FOUND` | 404 | `conditional approval case not found` | The `resumeApprovalCaseId` case does not exist, or belongs to another wallet or user. | Pass the case ID that the same wallet and user received. |
| `CONFLICT` | 409 | `request already has a different conditional approval case` | The `resumeApprovalCaseId` case was opened for a different `initiatorUserId`. | Resume with the same `initiatorUserId` that opened the case. |
| `FORBIDDEN` | 403 | `wallet has no trusted authorization scope binding` | The request needs approval, but the wallet is not bound to an authorization scope, so Para cannot choose approvers. | Bind the wallet with `PUT /v1/permissions-v2/wallets/{walletId}/authorization-scope`, then resend. |
| `INVALID_REQUEST` | 400 | `conditional approval requires an exact transaction signature subject` | The requirement collects transaction signatures, and Para could not derive the exact transaction from the request. | Send the complete transaction. |
| `INVALID_REQUEST` | 400 | `conditional approval stage exceeds supported limits` | Para cannot fill the first approval stage. It has no eligible approvers, fewer eligible approvers than `requiredApprovals`, or more than 25 eligible approvers when `requiredApprovals` is `"all"`. | Assign approvers who match the stage's eligibility, or adjust `requiredApprovals`. |
| `INVALID_REQUEST` | 400 | `approval stage resolves to more than 100 eligible people` | The approval stage has more than 100 eligible approvers. An approval that opens a later stage of more than 100 people returns this error too. | Narrow the stage's eligibility. |
| `INVALID_REQUEST` | 400 | `approval_amount_unavailable` | The deciding rule's approval requirement uses amount bands, and Para could not read a current amount for its `amountFact` from this request. | Send a request that carries the amount, or change the requirement's `amountFact`. |
| `CONFLICT` | 409 | `conditional approval authorization changed`, `conditional approval owner review changed` | Approver authority or the user's own review changed while Para opened the approval case. | Resend the request. |
| `INTERNAL_ERROR` | 500 | `permissions v2 review state missing` | A rule required approval on an SDK signing call, but Para opened neither a review nor an approval case. | Retry once. Contact Para if it repeats. |

<Warning>
  If a case completed on a user's own wallet but its approval expired before use, an identical transaction retry can return the same review even without `resumeApprovalCaseId`. Rebuild the transaction, for example with a fresh fee or nonce, then sign it without `resumeApprovalCaseId`.
</Warning>

<Note>
  An approval case is tied to its request: the `Idempotency-Key`, or the request body when you send no key. Once the case can no longer sign that request, every resend returns `POLICY_REVIEW_REQUIRED` with the same case. That happens when the case is `expired`, `invalidated` or `cancelled`, and when it is `completed` but was already used to sign or passed its seven-day lifetime. A `rejected` case returns `POLICY_DENIED` with `review-rejected` until its lifetime ends. To start over, resend with a new `Idempotency-Key`. Case statuses are described in [Approval requirements](/v3/references/permissions/approval-requirements).
</Note>

## Approval cases

These errors come from reading, deciding and cancelling approval cases, for example with `getAccessibleApprovalCase`, `approveApprovalCase`, `rejectApprovalCase` and `cancelApprovalCase` in the SDKs, or `GET /v1/permissions-v2/approval-cases` and `POST /v1/permissions-v2/approval-cases/{caseId}/cancel` on REST. For case statuses and the reasons a case ends, see [Approval requirements](/v3/references/permissions/approval-requirements).

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `FORBIDDEN` | 403 | `user is not a participant in this approval case` | The caller is not an eligible approver for the case, or approves a later stage that has not opened yet. | Hide the case. |
| `INITIATOR_CANNOT_REVIEW` | 403 | `user is not a participant in this approval case` | The caller started the request, or was named as its `initiatorUserId`, so cannot review it. This does not apply to a case that collects transaction signatures from the user who started it. | Show that the request is waiting for approvers. A user who signed the request reads it with `getInitiatedApprovalCase`. For a user named as `initiatorUserId`, read the case from your backend. |
| `FORBIDDEN` | 403 | `user did not initiate this approval case` | The caller read or cancelled a case that another user started. A user named as `initiatorUserId` also gets this from `getInitiatedApprovalCase`, because the signing call was not theirs. | Use `getAccessibleApprovalCase` for cases the user approves. |
| `CONFLICT` | 409 | `conditional approval case is not pending` | A cancel reached a case that is no longer `pending`, including a `completed` case that has not signed yet. | Read the case again and show its final status. |
| `NOT_FOUND` | 404 | `conditional approval case not found` | The case ID is unknown, or the case belongs to another project. | Check the case ID. |
| `INVALID_REQUEST` | 400 | `limit must be an integer from 1 to 100`, `beforeCaseId is invalid`, `beforeCaseId is not in this case list` | A paging parameter is out of range or unknown. | Fix the query. |
| `INVALID_REQUEST` | 400 | `caseId is invalid` | The case ID is malformed. | Pass the `caseId` from the case view. |
| `FORBIDDEN` | 403 | `authenticated user and partner project context required` | Your API key's app has no project. A call without a valid user session returns `401` `authentication required` instead. | Contact Para. |
| `FORBIDDEN` | 403 | `partner project context missing` | A REST call used an API key whose app has no project. | Contact Para. |

<Note>
  A user named as `initiatorUserId` can cancel the case with `cancelApprovalCase`, but cannot read it from the SDK. `getAccessibleApprovalCase` refuses with `INITIATOR_CANNOT_REVIEW`, `getInitiatedApprovalCase` refuses with `user did not initiate this approval case`, and neither list includes the case. Your backend reads the case with `GET /v1/permissions-v2/approval-cases/{caseId}` and gives the user its `caseId`.
</Note>

### Decision refusals

A refused approve or reject returns `403` with the message `approval decision is not allowed` and one of these codes.

<Warning>
  SDK calls receive decision refusals with their code: `approveApprovalCase` and `rejectApprovalCase` reject with `status: 403` and `error.code` set to one of the codes below. Read `decision` from the case before you show approve or reject. It is `{ canDecide: true, intents }` when the user can decide, or `{ canDecide: false, reason }`, where `reason` is `not_eligible`, `initiator`, `already_decided`, `case_terminal` or `authority_changed`.
</Warning>

| Code | HTTP | Meaning | What to do |
| - | - | - | - |
| `INITIATOR_CANNOT_APPROVE` | 403 | The user who started the request, or its `initiatorUserId`, tried to approve it. | Another eligible approver must approve. |
| `INITIATOR_CANNOT_REJECT` | 403 | The user who started the request, or its `initiatorUserId`, tried to reject it. | Another eligible approver must decide. To withdraw the request, cancel the case with `cancelApprovalCase`. |
| `ACTOR_NOT_ELIGIBLE` | 403 | The caller is not an approver in the active stage. | Check `decision.canDecide` before you offer the action. |
| `CASE_TERMINAL` | 403 | The case is no longer `pending`. | Read the case again and show its final status. |
| `ALREADY_COUNTED` | 403 | The caller already approved this case and tried to approve or reject it again. A reject after an approval is not recorded. | Treat a repeated approve as success. |
| `TRANSACTION_SIGNATURE_REQUIRED` | 403 | The case collects transaction signatures, and the approval had none. | Approve with `signApprovalTransaction` or `submitApprovalSignature`. |

### Approval signatures

These errors apply when an approval requirement collects transaction signatures (`evidence: "transaction_signature"`), for example with `createApprovalSignatureCollection`, `signApprovalTransaction` and `getApprovalSignatures`. Each signature must come from the user's own wallet of the collection's wallet type. `createApprovalSignatureCollection` opens an approval case, so it can also return the case-opening errors under [Review required](#review-required).

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `POLICY_DENIED` | 403 | `Transaction denied by policy` | Current policy denies the request, so Para will not open a signature collection for it. `deniedBy` names the deciding rule. | Change the request or the policy. |
| `PERMISSIONS_V2_EXTENSION_UNAVAILABLE` | 403 | `permissions v2 extension is not available for this partner` | Conditional approvals are not enabled for your app. | Contact Para to enable them. |
| `INVALID_REQUEST` | 400 | The first schema issue, or `invalid transaction signature collection` | The collection request is malformed. | Fix the field named in the message. |
| `INVALID_REQUEST` | 400 | `Invalid canonical transaction`, `invalid canonical transaction` | Para could not parse the transaction. | Fix the transaction payload. |
| `FORBIDDEN` | 403 | `an owned wallet registered with this partner is required` | The wallet is not the user's own wallet in your app, for example an app-owned wallet, or it cannot sign as the collection's wallet type. | Use the user's own wallet of the collection's type. |
| `CONFLICT` | 409 | `request already has a different conditional approval case` | The collection's `requestId` already belongs to a case for a different wallet or payload. | Use a new `requestId`. |
| `INVALID_REQUEST` | 400 | `this request does not select a transaction signature requirement` | `createApprovalSignatureCollection` reached a rule whose approval requirement collects approvals, not transaction signatures. | Sign the request to open an approval case, and approve it with `approveApprovalCase`. |
| `INVALID_REQUEST` | 400 | `no transaction signature requirement applies to this request` | No rule that collects signatures matches the request. | Check that a rule with `evidence: "transaction_signature"` covers it. |
| `INVALID_REQUEST` | 400 | `case does not contain a transaction signature collection` | The case does not collect signatures. | Approve it with `approveApprovalCase`. |
| `INVALID_REQUEST` | 400 | `invalid transaction signature contribution`, `collection signing uses its stored transaction payload` | A message signing call for a collection sent a malformed contribution, or its own transaction. | Sign with `signApprovalTransaction`, which signs the stored payload. |
| `FORBIDDEN` | 403 | `an authenticated wallet owner and enabled permissions are required` | The collection was signed without the wallet owner's session, or Permissions is not enabled for your app. | Sign as the wallet's owner. |
| `INVALID_REQUEST` | 400 | `signing message does not match the collection transaction` | The signed message is not the case's signing payload. | Sign the payload from `getApprovalSigningPayload`. |
| `FORBIDDEN` | 403 | `user cannot contribute to the current signing stage` | The caller cannot sign for the current stage: they are not eligible, already signed, or the case has ended. | Read the case and check `decision.reason`. |
| `FORBIDDEN` | 403 | `transaction signature contribution authorization is no longer current` | Policy or approver authority changed after the signature was authorized. | Read the case again, then sign again. |
| `FORBIDDEN` | 403 | `transaction signature contribution is denied by policy` | Current policy does not allow this wallet to sign for the case. | Do not retry with the same wallet. |
| `FORBIDDEN` | 403 | `transaction signature does not match an owned wallet and this request` | The signature is not from one of the user's wallets, or is not for this case's request. | Sign the case payload with a wallet the user owns. |
| `FORBIDDEN` | 403 | `signing wallet is outside this authorization scope` | The wallet is not bound to the case's authorization scope. | Sign with a wallet bound to that scope. |
| `FORBIDDEN` | 403 | `only the collection initiator can retrieve signatures` | Someone other than the user who started the collection asked for the signatures. | Call `getApprovalSignatures` as that user. |
| `FORBIDDEN` | 403 | `completed signature collection is denied by current policy` | The collection finished, but current policy no longer allows the request. | Start a new request. |
| `CONFLICT` | 409 | `transaction signature collection authorization is no longer current` | A policy that applies to this wallet or user changed, or the collection passed its seven-day lifetime. This also applies to a `completed` collection, including `getApprovalSignatures`, and a retry gets the same error. | Read the signatures soon after the collection completes. When this appears, start a new collection. |
| `CONFLICT` | 409 | `transaction signature collection is not complete` | The collection is not `completed`. It is still `pending`, or it was rejected, cancelled, invalidated or expired. | Wait while the case is `pending`. Otherwise start a new collection. |

## Approval administration

These errors come from managing who can approve: approver assignments, wallet authorization scope bindings and approval configuration. Your backend reaches them on `/v1/permissions-v2` REST routes, and configuration managers reach them through `updateApprovalConfiguration` and `resetApprovalConfiguration` in the SDKs. Every write takes an `expectedRevision` and an optional `force`.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `PERMISSIONS_V2_EXTENSION_UNAVAILABLE` | 403 | `permissions v2 extension is not available for this partner` | Conditional approvals are not enabled for your app. | Contact Para to enable them. |
| `APPROVAL_CASES_AFFECTED` | 409 | `change would invalidate open approval cases; retry with force to apply it` | The change would invalidate approval cases that are `pending`, or `completed` and not yet used to sign. `affectedCaseIds` lists them. In the SDKs, `updateApprovalConfiguration` and `resetApprovalConfiguration` throw `PermissionsV2ApprovalCasesAffectedError`. | Show the affected cases. To apply the change anyway, send it again with `force: true`, which invalidates them. |
| `CONFLICT` | 409 | `authority assignments changed; read the current revision and retry` | `expectedRevision` is out of date for the user's assignments. | Read the assignments, reapply your change and retry. |
| `CONFLICT` | 409 | `wallet authorization scope changed; read the current revision and retry` | `expectedRevision` is out of date for the wallet's scope binding. | Read the binding and retry. |
| `CONFLICT` | 409 | `conditional approval configuration changed; read the current revision and retry` | `expectedRevision` is out of date for the configuration. | Read the configuration and retry. |
| `INVALID_REQUEST` | 400 | `role <name> is not declared by an active policy`, `attribute <name>=<value> is not declared by an active policy` | The assignment names a role or attribute that no active policy uses in its eligibility. | Declare it in an active policy first. |
| `INVALID_REQUEST` | 400 | `active policies declare more than 100 role definitions` | Your active policies name more than 100 distinct roles. Attributes do not count. A write that only removes assignments is still accepted. | Reduce the roles your policies use. |
| `INVALID_REQUEST` | 400 | `a user may have at most 25 assignments in one partner project` | The user would have more than 25 assignments across every authorization scope in your project. | Remove assignments. |
| `INVALID_REQUEST` | 400 | `user is not linked to this app` | The assignments are for a user who has no wallet in your app. | Assign roles only to users with a wallet in your app. An empty `assignments` list is still accepted. |
| `INVALID_REQUEST` | 400 | `requirement_schema_invalid`, `safe_knob_not_declared`, `safe_knob_value_invalid`, `conditional approval configuration is invalid` | The configuration values do not fit the requirement. `safe_knob_not_declared` means a value names a setting the requirement does not declare in `safeKnobs`. `safe_knob_value_invalid` means a value has the wrong type or is out of range, such as a `minimum_amount` above the setting's `max`, above the stage threshold when there is no `max`, or below its `min`. | Fix the values. |
| `INVALID_REQUEST` | 400 | The first schema issue, or `beforeRevision must be a positive safe integer`, `limit must be an integer from 1 to 100`, `afterUserId must be a UUID`, `userId is invalid`, `authorizationScopeId is invalid`, `policyId is invalid`, `policyVersion is invalid`, `requirementId is invalid` | The body or a query parameter is malformed. | Fix the field named in the message. |
| `FORBIDDEN` | 403 | `wallet does not belong to the authenticated partner` | The wallet belongs to another app. | Use a wallet created for your app. |
| `FORBIDDEN` | 403 | `this requirement does not allow configuration managers`, `user is not an eligible configuration manager for this authorization scope` | A user without configuration-manager rights tried to change the configuration. | Use an eligible manager, or change it from your backend on REST. |
| `NOT_FOUND` | 404 | `user not found`, `wallet not found`, `wallet authorization scope not found`, `authority assignments not found`, `active conditional approval requirement not found`, `configurable conditional approval requirement not found`, `conditional approval configuration not found` | The named resource does not exist. | Check the IDs and the requirement reference. |

## Policy authoring

These errors come from creating, replacing, activating, deactivating and archiving policies with `para keys permissions` and in the Developer Portal. The Developer Portal shows each error's message and path next to the JSON. The CLI reports them as described under [CLI](#cli).

<Tip>
  Work through validation errors on your Development key, which runs on beta. A policy reaches Production only after a production review, so request one once the beta policy validates and behaves as expected.
</Tip>

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `INVALID_REQUEST` | 400 | The first validation error's message | The policy failed validation. `errors` lists the problems from the first check that failed, each with `code`, `path` and `message`. | Fix each error at its `path`. See [Policy validation codes](#policy-validation-codes). |
| `CONFLICT` | 409 | The first error's message | The write conflicts with an existing policy record. `errors[0].code` is one of the conflict codes under [Policy records](#policy-records). | Read the current policy state, then change `version` or resolve the conflict. |
| `INVALID_REQUEST` | 400 | The first error's message | A lifecycle action failed. `errors[0].code` is a [Policy records](#policy-records) code, such as `policy_record_not_found` or `policy_record_archive_active`, or a validation code when activation checks the policy again. | Fix what the code names. Deactivate a policy before you archive it. |
| `INVALID_REQUEST` | 400 | `activation metadata belongs on policy creation; create a new policy version to change scheduling` | An activate call included `activation`. | Set activation when you create the policy version. |
| `NOT_FOUND` | 404 | `permissions v2 policy not found` | The policy record does not exist for this key. | Check the record ID and the key. |
| `FORBIDDEN` | 403 | `production policy review is only available for prod API keys` | A production review was requested on a Development key. | Request it from the Production key. |
| `APPROVAL_CASES_AFFECTED` | 409 | `change would invalidate open approval cases; retry with force to apply it` | Replacing, activating or deactivating the policy would invalidate approval cases that are `pending`, or `completed` and not yet used to sign. `affectedCaseIds` lists them. | Review the cases, then send the change again with `"force": true` in the body. In the CLI, pass `--force`. |
| `INVALID_REQUEST` | 400 | `force must be a boolean` | `force` in an activate or deactivate body is not `true` or `false`. On replace, a non-boolean `force` fails validation with `partner_policy_schema_invalid` at `$.force` instead. | Send a boolean, or omit `force`. |

<Note>
  Validation without saving, such as `para keys permissions validate`, returns `200` with `{ "valid": false, "errors": [...] }` instead of an error. Requesting a production review returns `409` with `{ "status": "ineligible" }` when no Development key has an active policy or the Production key is not awaiting review, and `503` with `{ "status": "delivery-failed" }` when the request could not be delivered.
</Note>

## User parameters

These errors come from reading and saving a user's adjustable limits: `listUserAdjustablePolicies`, `getUserPolicyParameters`, `listUserPolicyParameterHistory`, `updateUserPolicyParameters` and `resetUserPolicyParameters` in the SDKs, and the `GET /v1/permissions-v2/users/{userId}/user-parameters` routes on REST. SDK calls receive them as plain text.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `INVALID_REQUEST` | 400 | `userId is invalid`, `beforeRevision must be a positive safe integer` | REST: the user ID is malformed. History reads: the paging value is not a positive whole number. | Fix the query. |
| `FORBIDDEN` | 403 | `user has no wallet connection to the authenticated partner` | REST: the user has no wallet in your app. | Check the user ID. |
| `NOT_FOUND` | 404 | `user parameter policy not found` | The policy is not active or has no adjustable parameters. | Pick a policy from `listUserAdjustablePolicies`. |
| `FORBIDDEN` | 403 | `user parameter policy is not visible to this scope` | The policy's selector does not cover this user or app. | Check the user and the policy's selector. |
| `CONFLICT` | 409 | `user parameter policy changed; reload settings` | The policy changed after the settings were loaded. | Reload with `getUserPolicyParameters` and save again. |
| `CONFLICT` | 409 | `user parameter revision changed; reload settings` | `expectedRevision` is out of date. | Reload and save again. |
| `INVALID_REQUEST` | 400 | `user parameter values are invalid` | A value names an unknown parameter, or an amount is not in canonical form. A value of the wrong type fails the body schema first, with the schema's message. | Fix the values. |
| `INTERNAL_ERROR` | 500 | `user parameter policy record is invalid`, `user parameter policy record cannot be projected`, `user parameter change could not be prepared` | Para could not read or prepare the settings from the stored policy. | Contact Para. |
| `PERMISSIONS_V2_EXTENSION_UNAVAILABLE` | 403 | `permissions v2 extension is not available for this partner` | User-adjustable parameters are not enabled for your app. | Contact Para to enable them. |

<Note>
  Saved values belong to one version of a policy. After you publish a new version, each user who saved their own values must confirm them. `getUserPolicyParameters` returns `status: "needs_confirmation"` with the earlier values in `previousValues`, and signing under that policy is denied with `parameter_configuration_invalid` until the user saves or resets their values. Users who never saved values get the new defaults.
</Note>

## Policy reads, usage and consent

These errors come from reading policies, a user's spending against a policy and a user's consent. In the SDKs, that is `listPolicies`, `getPolicy`, `listApplicablePolicies`, `getApplicablePolicy`, `getUserPolicyUsage`, `listUserPolicyConsents` and `getUserPolicyConsent`. On REST, it is the `/v1/permissions-v2/policies`, `/v1/permissions-v2/users/{userId}/policies/{policyId}/usage`, `/v1/permissions-v2/wallets/{walletId}/policies/{policyId}/usage` and `/v1/permissions-v2/users/{userId}/delegation-consents` routes. SDK calls receive them as plain text. When Permissions is not enabled for your app, the list reads return empty lists.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `FORBIDDEN` | 403 | `partner context missing`, `partner project context missing` | Para could not resolve your app's project from the API key. | Check the secret API key. Contact Para if it is correct. |
| `NOT_FOUND` | 404 | `permissions v2 policy not found` | The policy does not exist or does not apply to your app. | Check the policy ID. |
| `INTERNAL_ERROR` | 500 | `permissions v2 policy record is invalid` | Para could not read a stored policy record. | Contact Para. |
| `INVALID_REQUEST` | 400 | `userId is required`, `userId is invalid`, `walletId is required`, `walletId is invalid` | A required ID is missing or malformed. | Fix the query. |
| `FORBIDDEN` | 403 | `wallet is not owned by the authenticated user` | SDK: the wallet does not belong to the signed-in user. | Pass one of the user's wallets. |
| `FORBIDDEN` | 403 | `wallet has no connection to the authenticated partner`, `wallet is not owned by the authenticated partner`, `user wallet has no connection to the authenticated partner` | REST: the wallet is not in your app, or is not the user's wallet in your app. | Check the wallet and user IDs. |
| `INVALID_REQUEST` | 400 | `chainId must be a positive EVM chain ID`, `chainId is invalid`, `chainId is invalid for this wallet type`, `chainId does not match this policy`, `tokenAddress must be an EVM address`, `tokenAddress must be an exact token identity`, `policy usage is available for supported transaction spend only`, `policy usage is available for EVM transaction spend only` | The usage query does not fit the policy or the wallet. | Pass a chain and token that the policy's spending limit covers. |
| `CONFLICT` | 409 | `user parameter policy changed; reload settings` | Usage: the user's saved values need confirmation after a new policy version. | Have the user save or reset their values. |
| `FORBIDDEN` | 403 | `delegation consent partnerId must match authenticated API key`, `delegation consent appId must match authenticated API key` | A consent read named a different app than your API key. | Omit those fields, or pass your own app's values. |
| `NOT_FOUND` | 404 | `delegation policy not found` | The consent read named a policy that does not exist. | Check the policy ID. |
| `INVALID_REQUEST` | 400 | `policy is not a delegation policy` | The policy is a Guardrail policy. Consent applies only to Request policies. | Pass a Request policy. |
| `FORBIDDEN` | 403 | `policy is not visible to this partner`, `policy is not visible to this app`, `policy is not visible to this user`, `policy is not visible to this wallet` | The policy's selector does not cover this app, user or wallet. | Check the IDs against the policy's selector. |

## Pending transaction review

When a `require_approval` rule has no approval requirement, `POLICY_REVIEW_REQUIRED` carries a `reviewId`. Your backend can approve or reject reviews from REST or standard app-owned SDK signing with your app's server secret through `POST /v1/permissions-v2/reviews/{reviewId}/approve` or `/reject`. Both return `200` with the review's current `status`, even when the review has already ended as `expired`, `rejected` or `consumed`.

After `approved`, retry identical SDK signing input without `resumeApprovalCaseId` only while the approval is unused and unexpired and the request and policy binding still match. Para re-evaluates the request, so approval status alone does not guarantee a signature. The Para Portal handles a user's own pending transactions.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `NOT_FOUND` | 404 | `review not found` | The `reviewId` does not exist. | Check the `reviewId` from `POLICY_REVIEW_REQUIRED`. |
| `FORBIDDEN` | 403 | `review does not belong to the authenticated partner` | The review's partner or signing actor does not match the authenticated app. | Use your app's server secret for reviews whose partner and signing actor both identify your app. |
| `FORBIDDEN` | 403 | `partner context missing` | Para could not resolve your app from the API key. | Check the secret API key. Contact Para if it is correct. |
| `CONFLICT` | 409 | `review approval is in flight` | The approved review is being used by a signing call right now. | Call again later. The response shows `consumed` once the request is signed, or `approved` if that signing attempt was released. |
| `PENDING_TRANSACTION_ALREADY_APPROVED` | 409 | `pending transaction already approved` | The requester tried to reject a held transaction already approved by a matching completed case with `authenticated_approval` evidence, with no separate ordinary review. The approval was used or is still unexpired, and no lifecycle change invalidates the case. | Do not repeat the rejection. To withdraw the request, cancel its approval case while it is still pending. |

## Idempotency

REST signing identifies a request by its `Idempotency-Key`, or by the request body when you send no key. Para also uses that identity to tie the request to its approval case. Spend reservations use the `Idempotency-Key` only.

| Code | HTTP | Message | Meaning | What to do |
| - | - | - | - | - |
| `CONFLICT` | 409 | `A request with this idempotency key is currently being processed` | A request with this key is still running. | Wait, then retry with the same key. |
| `UNPROCESSABLE` | 422 | `Idempotency key has already been used with different request parameters` | The key was already used with a different body, and the first request is still running or succeeded. | Use a new key for a different request. |
| `IDEMPOTENCY_CONFLICT` | 409 | `This key identifies a different transfer` | The key already belongs to a retained sponsored transfer or a transfer with automatic retries, and this request uses another route, wallet or transfer body. | Keep the original key when retrying the original transfer. Use a new key for a different request. |
| `INVALID_REQUEST` | 400 | `Idempotency-Key must be 256 characters or fewer`, `Idempotency-Key contains invalid characters` | The key is malformed. | Send a UUID. |
| `CONFLICT` | 409 | `request already has a different conditional approval case` | The key, or the same body without a key, already belongs to an approval case for a different payload, wallet or `initiatorUserId`. Error responses are not cached, so reusing a key after a `403` reaches this check instead of the `422` above. | Use a new key for a different request. |
| `CONFLICT` | 409 | `Stable signing request id was reused with a different payload` | The `Idempotency-Key` was reused with a different spend payload. | Use a new key. |
| `CONFLICT` | 409 | `Spend reservation is unavailable for signing`, `Signing reservation already records a different protocol` | The spend reservation for this request identity was already used, or belongs to another signing attempt. | Use a new key. |

## SDK error classes

The SDKs raise these classes in addition to the server codes above. The review classes come from the JavaScript SDKs when a signing call needs review. The REST SDK also throws `ParaRestValidationError` before it sends a request, with the message `<name> is required`, when a required ID or body is empty.

| Class | HTTP | Meaning | What to do |
| - | - | - | - |
| `ParaRestError` | n/a | Any non-2xx response in the REST SDK. Carries `status`, `code`, `requestId` and the parsed `body`, plus `affectedCaseIds` for `APPROVAL_CASES_AFFECTED`. | Branch on `code`. Read `body.deniedBy` or `body.approvalCaseId`. |
| `PermissionsV2ApprovalCasesAffectedError` | 409 | `updateApprovalConfiguration` or `resetApprovalConfiguration` received `APPROVAL_CASES_AFFECTED`. Carries `code`, `status` and `affectedCaseIds`. | Confirm with the user, then call again with `force: true`. For `resetApprovalConfiguration`, pass `{ expectedRevision, force: true }`. |
| `TransactionReviewError` | n/a | The base class of `TransactionReviewRequired`. Carries `transactionReviewUrl`. `TransactionReviewDenied` and `TransactionReviewTimeout` do not extend it. | Use `instanceof TransactionReviewError` to catch a required review. Check the denied and timeout classes separately. |
| `TransactionReviewRequired` | n/a | A signer integration, such as viem, ethers, Solana, Stellar, Sui or CosmJS, received a pending review instead of a signature. Extends `TransactionReviewError` and carries `transactionReviewUrl` and `pendingTransactionId`. | Send the user to `transactionReviewUrl`, then sign again after approval. |
| `TransactionReviewDenied` | n/a | While the SDK waited for review, it could not read the pending transaction, for example because the user rejected it, an approver rejected its approval case, its approval case was cancelled, or a network error interrupted the wait. | Treat the request as not signed. Do not retry automatically. |
| `TransactionReviewTimeout` | n/a | Review did not finish in time, or your `isCanceled` callback stopped the wait. When review is required, the SDK waits at least five minutes. Carries `transactionReviewUrl` and `pendingTransactionId`. | Check the approval case before you sign again. If it is still `pending`, let the user finish at `transactionReviewUrl`. |
| `SigningResultError` | n/a | The signing call returned neither a signature nor a pending transaction. Carries `code`, `status` and `data` like other SDK API errors. | Branch on `code`. |

## CLI

The CLI receives every error as JSON but prints its own message and exit code. Run with `--json` to read `errorCode`, `exitCode` and `details`.

<Note>
  `create` and `replace` validate the policy before they write it, and report an invalid policy the same way as `para keys permissions validate`. The CLI prints each error, or `{ "valid": false, "errors": [...] }` with `--json`, and exits with `10` without an `errorCode`. `details.errors` appears with `API_VALIDATION_ERROR` only when the write or lifecycle call itself is rejected, for example when `activate` finds that conditional approvals or adjustable parameters are not enabled for your app (`permissions_v2_extension_unavailable`). For other API errors, including any `403`, the CLI prints its own guidance in place of the server message.
</Note>

| `errorCode` | HTTP | Exit | Meaning | What to do |
| - | - | - | - | - |
| `API_VALIDATION_ERROR` | 400, 409 | 10 | A policy write or lifecycle call was rejected with field errors in `details.errors`. | Fix each error at its `path`. |
| `API_UNAUTHORIZED` | 401 | 3 | The API rejected your session. | Run `para login`. |
| `API_FORBIDDEN` | 403 | 5 | Your account cannot access the organization, project or key. | Check your access to the organization, project and key. |
| `API_NOT_FOUND` | 404 | 6 | The key or policy record does not exist. | Check the key and record IDs. |
| `API_RATE_LIMITED` | 429 | 7 | You sent too many requests. | Wait and retry. |
| `API_SERVER_ERROR` | 5xx | 11 | The API failed. Policy writes report `PERMISSIONS_V2_WRITE_OUTCOME_UNKNOWN` instead. | Retry later. |
| `API_UNKNOWN_ERROR` | Other | 1 | The API returned an error the CLI does not map, such as a `400` or `409` without field errors. `force must be a boolean` is one example. | Read the message and check the command input. |
| `NETWORK_ERROR` | n/a | 8 | The CLI could not reach the API, or the request timed out, after its retries. Policy writes report `PERMISSIONS_V2_WRITE_OUTCOME_UNKNOWN` instead. | Check your network connection and retry. |
| `PERMISSIONS_BETA_KEY_REQUIRED` | n/a | 2 | A policy change targeted a key that is not a beta key. | Select `--environment beta` or a beta API key. |
| `PERMISSIONS_V2_APPROVAL_CASES_AFFECTED` | 409 | 10 | `replace`, `activate` or `deactivate` received `APPROVAL_CASES_AFFECTED` (`409`). `details.affectedCaseIds` lists the cases. In a terminal, the CLI asks before it applies the change anyway. With `--yes` and no `--force`, it exits with this code instead. | Rerun with `--force` to apply the change and invalidate those cases. |
| `PERMISSIONS_V2_WRITE_OUTCOME_UNKNOWN` | 5xx or n/a | 8 | A policy create, replace or lifecycle write lost its response to a network or server error, so it may have applied. | Run `para keys permissions state` before you retry. |
| `PERMISSIONS_V2_POLICY_JSON_INVALID` | n/a | 10 | The policy file is not valid JSON. | Fix the JSON syntax. |
| `PERMISSIONS_V2_POLICY_JSON_OBJECT_REQUIRED` | n/a | 10 | The policy file's root is not a JSON object. | Put one policy object at the root. |
| `PERMISSIONS_V2_POLICY_STDIN_REQUIRES_PIPE` | n/a | 2 | `--file -` was used without piped input. | Pipe the JSON in, or pass a file path. |
| `USAGE_ERROR` | n/a | 2 | `--file` is missing, or `--json` was used on `replace`, `activate`, `deactivate` or `archive` without `--yes`. | Add the missing flag. |

Before it calls the API, the CLI can also exit with `AUTH_ERROR` when you are not logged in (exit `3`) or your session has expired (exit `4`), and with `CONFIG_NO_ORG_AND_PROJECT` (exit `9`) when no organization and project are selected.

## Denial reasons

`deniedBy[0].reason` explains a `POLICY_DENIED` response. `policyId` and `ruleId` name the deciding rule and are omitted when no rule won. A rule with `effect: "require_approval"` reports its rule the same way in the `reason` of `POLICY_REVIEW_REQUIRED`. So does an `allow` rule that Para holds for the wallet owner's review, and Para's baseline review of a user-owned wallet, which reports `matched:para:review-user-owned`.

<Info>
  When a rule needs a fact that Para cannot read for a request, the rule's outcome is unknown. Para denies the request with the fact reason when that rule's effect outranks the rule that would otherwise decide, or when no other rule matched. It does not fall back to Para's baseline policies in that case.
</Info>

| Reason | Meaning | What to do |
| - | - | - |
| `matched:<layer>:<ruleId>` | A `deny` rule matched. `<layer>` is `partner` for your policies and `para` for Para's. | Change the request or the rule. |
| `default-deny` | No rule decided the request, not even Para's baseline. When none of your rules decides, the baseline normally does: it signs for an app-owned wallet and sends a user-owned wallet to review. | Contact Para with the request. |
| `review-rejected` | An approver rejected the approval for this exact request. | Do not resend it. Submit a new request if appropriate. |
| `approval-reservation-unavailable` | An approval allowed the request, but Para could not reserve that approval for this signature. Usually another signing call already used it, and `policyId` and `ruleId` name the approved rule. | Do not resend with the same approval. If nothing else used it, contact Para with the request. |
| `windowed-spend-limit-exceeded` | The policy allowed the request, but other spending, usually from concurrent requests, filled a spending limit's window before Para reserved this amount. `policyId` and `ruleId` name the rule that allowed the request. A request that is already over the limit when Para evaluates it gets the limit rule's own reason, or goes to review. | Wait for the window to reset, or lower the amount. |
| `network-unverifiable` | Para could not prove which network the request targets, for example because a Solana transaction's blockhash expired or a network read failed. | Rebuild the transaction with a fresh blockhash, or send a request whose network Para can verify. |
| `coin-evidence-unverifiable` | Para could not read the Sui coin objects in the request from the network. References that do not match the transaction return `400` `Sui transaction object references could not be resolved` instead. | Retry later. |
| `unsupported-request` | Para cannot classify the request, for example a smart account operation that is not a managed ERC-20 transfer. | Use a supported request type. |
| `missing-fact` | A condition reads a fact that this request does not produce, such as `request.actionFamilies` on a raw signature. | Limit the rule to requests that produce the fact, or change the condition. |
| `unknown-fact` | Para had no value for the condition's fact. | Contact Para with the request. |
| `error-fact` | Reading a fact failed, such as a wallet or spending read. | Retry later. Contact Para if it repeats. |
| `record_hash_invalid` | A stored policy record failed Para's integrity check. | Contact Para. |
| `policy_invalid` | An active policy no longer passes validation, or an internal read failed while Para evaluated the request. | Retry once. If it repeats and your active policies validate, contact Para. Otherwise fix and republish the policy. |
| `parameter_configuration_unavailable` | Para could not load the user's adjustable parameters, for example because they are not enabled for your app. | Check that adjustable parameters are enabled for your app. Otherwise retry, and contact Para if it repeats. |
| `parameter_configuration_invalid` | The user saved values for an earlier version of the policy. Saved values need the user's confirmation after every new version. | Have the user save or reset their values. See [User parameters](#user-parameters). |
| `binding_indeterminate` | Para could not decide whether a policy applies to this request. | Retry. Contact Para if it repeats. |
| `ambiguous_duplicate` | Para found two active versions of the same policy. | Contact Para. |

## Policy validation codes

Policy writes and validation return these codes in `errors[].code`, each with a `path` such as `$.rules[0].when` and a `message`. A value that does not match the policy schema returns `partner_policy_schema_invalid`, and a key the schema does not define returns `policy_schema_field_unknown`. The other codes report problems that the schema alone does not catch. Read `path` and `message` together. For the policy format itself, see [Policy JSON schema](/v3/references/permissions/policy-json) and [Facts and conditions](/v3/references/permissions/facts).

<Note>
  Leave `layer` out of policies you write for the CLI or the Developer Portal. Para sets it. A `layer` other than `"partner"` fails validation at `$.layer`: `"para"` returns `policy_layer_owner_mismatch`, and any other value returns `layer_invalid`.
</Note>

### Schema

Schema codes identify unrecognized fields, malformed documents or a missing policy mode.

| Code | Meaning |
| - | - |
| `policy_schema_field_unknown` | The policy has a key the schema does not define, at any level. `path` names the key, and `message` is `unknown policy field <key>`. |
| `partner_policy_schema_invalid` | A value does not match the policy schema. Examples are a wrong `schemaVersion`, a missing or empty `id` or `version`, an empty `rules` list, an unknown action or effect, and a malformed condition, metadata value or approval requirement. `message` describes the problem. |
| `partner_policy_mode_required` | `metadata.productMode` is missing. |
| `layer_invalid` | `layer` is not `"partner"` or `"para"`. `path` is `$.layer`. |

### Selector

Selector codes identify invalid wallet, user, network or app targeting.

| Code | Meaning |
| - | - |
| `selector_invalid` | `selector` is not an object. |
| `selector_list_invalid` | A `walletId`, `userId` or `chainId` list is empty or repeats a value. A Solana or Sui `chainId` list also cannot name the same network twice in different forms. `walletId` and `userId` lists allow at most 100 entries, and `chainId` has no maximum. |
| `selector_wallet_type_required` | `selector.walletType` is missing. |
| `selector_network_wallet_type_mismatch` | A network belongs to a different wallet type. |
| `selector_value_not_authorable` | You cannot target this value, such as an unknown wallet type or network, a placeholder, `unscoped`, or a target outside your app. |
| `selector_not_partner_selectable` | The selector field is reserved for Para. |
| `selector_user_mode_invalid` | `userId` is set on a Guardrail policy. |
| `selector_wallet_user_conflict` | `walletId` and `userId` are both set. |
| `server_owned_selector_mismatch` | `partnerId` or `appId` differs from the app your key belongs to, in a policy that includes `layer`. Without `layer`, Para replaces both with your app's values. |

### Rules

Check these codes for repeated rule IDs, unsupported actions and effects that the ownership model does not allow.

| Code | Meaning |
| - | - |
| `rule_id_duplicate` | Two rules share an `id`. |
| `rule_action_unsupported` | An action is not available for the policy's wallet type. |
| `policy_layer_owner_mismatch` | The policy sets `layer` to `"para"`. Remove `layer`. |
| `guardrail_effect_invalid` | A Guardrail rule uses `allow`. Guardrail rules `deny` or `require_approval`, so remove the rule or change its effect. An `allow` on exactly `["raw_signature"]` is still accepted for older policies and has the same effect as Para's baseline. |
| `guardrail_signature_collection_invalid` | A Guardrail approval requirement uses `evidence: "transaction_signature"`. |
| `delegation_effect_invalid` | A Request policy rule uses `deny`, or uses `require_approval` without an `approvalRequirement`. |

### Conditions and facts

These codes cover fact availability, operator and value compatibility, condition structure and metering declarations.

| Code | Meaning |
| - | - |
| `max_condition_count_exceeded` | A rule has more than 12 condition nodes. |
| `max_condition_depth_exceeded` | Conditions are nested more than 6 levels deep. |
| `fact_unknown` | The fact key does not exist. |
| `fact_not_authorable` | The fact exists but cannot be used in policies. |
| `fact_wallet_type_unsupported` | The fact is not available for the policy's wallet type. |
| `fact_action_unsupported` | The fact, or the requirement's `amountFact`, is not available for one of the rule's actions. This is common with `actions: "all"`. |
| `operator_not_allowed_for_fact` | The fact does not support this operator. |
| `value_not_allowed_for_fact` | The value is outside the fact's allowed values. |
| `value_type_mismatch` | The value has the wrong type, such as an amount sent as a JSON number instead of a string, or `in` without an array. |
| `chain_identity_invalid` | An address or asset is not in canonical form for the wallet type. Ordinary EVM identity strings are unchecked, and matching depends on the operator: a malformed expectation can match `ne` against a present canonical address. Aggregate token `asset` values are checked; on EVM, `wallet.spend.erc20`, `wallet.spend.token` and `safe.spend.erc20` require `0x` followed by 40 hexadecimal characters. |
| `aggregate_fact_not_allowed` | An `aggregate` condition uses a fact that is not metered. |
| `metered_fact_window_required` | A metered fact is used in a `compare` condition. Use `aggregate` with a `window`. |
| `metered_fact_window_invalid` | The `window` value is invalid. |
| `metered_fact_window_conflict` | The policy uses two different windows for the same metered fact. |
| `metered_fact_asset_required` | A non-EVM `wallet.spend.token` condition has no `asset`. |
| `metered_fact_asset_not_allowed` | A native spend condition sets `asset`. |

### Parameters

Parameter codes cover IDs, default values and where adjustable values can be declared.

| Code | Meaning |
| - | - |
| `parameter_id_duplicate` | Two parameters share an `id`. |
| `parameter_not_allowed` | A parameter is used where it is not allowed, such as inside `not`, on an unsupported fact or with an unsupported operator. |
| `parameter_value_invalid` | A parameter's default value is not in canonical form. |

### Approval requirements

These codes report repeated requirement IDs or features that are not enabled for the app.

| Code | Meaning |
| - | - |
| `approval_requirement_id_duplicate` | Two approval requirements share an `id`, in a policy that includes `layer`. Without `layer`, the same problem returns `partner_policy_schema_invalid` with the message `approval requirement identifiers must be unique within a policy`. |
| `permissions_v2_extension_unavailable` | The policy uses adjustable parameters or approval requirements, and they are not enabled for your app. The message says which. |

A `minimum_amount` setting in `safeKnobs` can declare a `min` and `max` range. If `min` is above `max`, or the stage's `minimumAmount` falls outside the range, validation returns `partner_policy_schema_invalid` at `$.rules[i].approvalRequirement.safeKnobs[j]` with the message `minimum configurable amount threshold must not exceed maximum` or `the published amount threshold must be within the configurable range`.

### Policy records

These codes come from saving and changing policy records. The conflict codes return `409`. The others return `400`.

| Code | Meaning |
| - | - |
| `policy_id_reserved` | The policy `id` starts with `para-`, in any letter case. That prefix is reserved for Para. |
| `policy_record_not_found` | The policy record does not exist in the state the action needs. |
| `policy_record_archive_active` | An active policy cannot be archived. Deactivate it first. |
| `policy_record_activation_conflict` | The draft was already activated or no longer exists. |
| `policy_record_role_definition_limit` | Activating would name more than 100 distinct roles across your app's active policies. Attributes do not count. |
| `policy_record_activation_effective_from_invalid` | `activation.effectiveFrom` is not a valid UTC ISO timestamp. |
| `policy_record_activation_effective_until_invalid` | `activation.effectiveUntil` is not a valid UTC ISO timestamp. |
| `policy_record_activation_window_invalid` | `activation.effectiveUntil` is not after `activation.effectiveFrom`. |
| `policy_record_activation_window_inactive` | The policy's activation window does not include the current time, so it cannot be activated now. Activate it inside the window, or set a window that includes the current time. |
| `policy_edit_activation_window_inactive` | An edit to an active policy needs both the current and the new activation windows to include the current time. |
| `policy_record_not_normalized` | The draft no longer matches its normalized form. Create a new draft. |
| `policy_record_integrity_invalid` | The draft failed its integrity check. Create a new draft. |
| `policy_record_contract_invalid` | The record no longer meets the current policy contract. Deactivate or archive it and create a new policy. |
| `policy_record_immutable_version_conflict` | Conflict. The same `id` and `version` already exist with different content. |
| `policy_record_version_already_exists` | Conflict. The `id` and `version` already exist outside the current draft. Create a new version. |
| `policy_record_draft_already_exists` | Conflict. A draft already exists for this product mode. Activate or archive it first. |
| `policy_record_product_mode_conflict` | Conflict. The policy `id` already belongs to the other product mode. |
| `policy_record_identity_conflict` | Conflict. An edit changed the policy `id`. |
| `policy_record_selector_conflict` | Conflict. An edit changed `selector`. A different selector makes a separate policy. |
| `policy_record_version_conflict` | Conflict. An edit used the wrong `version`. The message names the expected one. |
| `policy_record_activation_enables_other_mode` | Conflict. Activating would also turn on a policy in the other product mode. |

<CardGroup cols={3}>
  <Card imgUrl="/images/v3/cards/concept-approval-requirements.png" title="Approval requirements" description="Approval stages, case statuses and the reasons a case ends." href="/v3/references/permissions/approval-requirements" />

  <Card imgUrl="/images/v3/cards/concept-policy-json.png" title="Policy JSON schema" description="The policy document format that validation codes refer to." href="/v3/references/permissions/policy-json" />

  <Card imgUrl="/images/v3/cards/cli-policies.png" title="Author policies with the CLI" description="Validate and publish policies with para keys permissions." href="/v3/cli/permissions" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.