> ## 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.

# Manage the policy lifecycle

> Version, schedule and retire Request policies, and handle what each change means for your mobile users

export const PermissionsCards = ({basePath, pages, cols = 2, horizontal = false}) => {
  const ImageCard = Card;
  const hrefOverrides = {
    "/v3/react/guides/permissions": {
      "conditional-approvals": "/v3/react/guides/conditional-approvals"
    },
    "/v3/rest/permissions": {
      "backend": "/v3/rest/permissions-administration"
    }
  };
  const guardrailBases = ["/v3/rest/permissions", "/v3/server/guides/permissions"];
  const ownership = guardrailBases.includes(basePath) ? 'guardrail' : 'request';
  const overviewImages = {
    "/v3/rest/permissions": {
      "imgUrl": "/images/v3/cards/rest-guardrails.png",
      "horizontalImgUrl": "/images/v3/cards/rest-guardrails-horizontal.png"
    },
    "/v3/server/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-server-guardrail-signing.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-server-guardrail-signing-horizontal.png"
    },
    "/v3/react/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-get-policy-consent-url.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-get-policy-consent-url-horizontal.png"
    },
    "/v3/react-native/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-get-policy-consent-url.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-get-policy-consent-url-horizontal.png"
    }
  };
  const registry = {
    overview: {
      title: "Permissions overview",
      description: "Return to the start of the Permissions path.",
      ...overviewImages[basePath]
    },
    "developer-portal": {
      title: "Create policies in the Developer Portal",
      description: "Build a policy with the Portal forms and activate it on a beta key.",
      imgUrl: "/images/v3/cards/developer-portal-policy-form.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-form-horizontal.png"
    },
    "json": {
      title: "Write policy JSON",
      description: "Move to JSON for policies the forms cannot express.",
      imgUrl: "/images/v3/cards/developer-portal-policy-json.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-json-horizontal.png"
    },
    "testing": {
      title: "Test your policies",
      description: "Exercise every rule on beta before you ship.",
      imgUrl: "/images/v3/cards/concept-policy-testing.png",
      horizontalImgUrl: "/images/v3/cards/concept-policy-testing-horizontal.png"
    },
    "production": {
      title: "Go to production",
      description: "Request a production review and publish your policy.",
      imgUrl: "/images/v3/cards/concept-policy-publication.png",
      horizontalImgUrl: "/images/v3/cards/concept-policy-publication-horizontal.png"
    },
    "conditional-approvals": {
      title: "Conditional approvals",
      description: "Require people to approve a request before it signs.",
      imgUrl: "/images/v3/cards/portal-conditional-approval-case.png",
      horizontalImgUrl: "/images/v3/cards/portal-conditional-approval-case-horizontal.png"
    },
    "approval-signatures": {
      title: "Collect approval signatures",
      description: "Collect signatures from approvers for multisig workflows.",
      imgUrl: "/images/v3/cards/sdk-get-approval-signatures.png",
      horizontalImgUrl: "/images/v3/cards/sdk-get-approval-signatures-horizontal.png"
    },
    "adjustable-parameters": {
      title: "Adjustable parameters",
      description: "Let users set their own limits and managers adjust approval settings.",
      imgUrl: "/images/v3/cards/portal-user-policy-limits.png",
      horizontalImgUrl: "/images/v3/cards/portal-user-policy-limits-horizontal.png"
    },
    "lifecycle": {
      title: "Manage the policy lifecycle",
      description: "Version, activate, replace and retire policies.",
      imgUrl: "/images/v3/cards/developer-portal-policy-versions.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-versions-horizontal.png"
    },
    "backend": {
      title: "Manage permissions from your backend",
      description: "Assign approvers and read policy state with your server secret.",
      imgUrl: "/images/v3/cards/rest-permissions-administration.png",
      horizontalImgUrl: "/images/v3/cards/rest-permissions-administration-horizontal.png"
    },
    templates: ownership === "guardrail" ? {
      title: "Guardrail templates",
      description: "Start from a tested Guardrail policy.",
      imgUrl: "/images/v3/cards/concept-guardrail-templates.png",
      horizontalImgUrl: "/images/v3/cards/concept-guardrail-templates-horizontal.png"
    } : {
      title: "Request templates",
      description: "Start from a tested Request policy.",
      imgUrl: "/images/v3/cards/concept-request-templates.png",
      horizontalImgUrl: "/images/v3/cards/concept-request-templates-horizontal.png"
    },
    "guardrail:templates/spending-caps": {
      title: "Spending caps",
      description: "Cap native and token transfers per transaction or per day.",
      imgUrl: "/images/v3/cards/concept-spending-limits.png",
      horizontalImgUrl: "/images/v3/cards/concept-spending-limits-horizontal.png"
    },
    "guardrail:templates/allowlists": {
      title: "Recipient and contract allowlists",
      description: "Limit transfers and contract calls to addresses you know.",
      imgUrl: "/images/v3/cards/concept-recipient-allowlists.png",
      horizontalImgUrl: "/images/v3/cards/concept-recipient-allowlists-horizontal.png"
    },
    "guardrail:templates/approval-tiers": {
      title: "Approval thresholds and tiers",
      description: "Hold large transfers for approval, or deny the largest.",
      imgUrl: "/images/v3/cards/concept-approval-tiers.png",
      horizontalImgUrl: "/images/v3/cards/concept-approval-tiers-horizontal.png"
    },
    "guardrail:templates/treasury": {
      title: "Treasury approvals",
      description: "Deny unplanned actions and approve wallet or Safe payouts in stages.",
      imgUrl: "/images/v3/cards/concept-treasury-approvals.png",
      horizontalImgUrl: "/images/v3/cards/concept-treasury-approvals-horizontal.png"
    },
    "guardrail:templates/message-signing": {
      title: "Message and raw signing",
      description: "Block or hold raw signatures, and block permits.",
      imgUrl: "/images/v3/cards/concept-signing-actions.png",
      horizontalImgUrl: "/images/v3/cards/concept-signing-actions-horizontal.png"
    },
    "request:templates/allowances": {
      title: "Transfer allowances",
      description: "Let small transfers and a daily budget sign without a prompt.",
      imgUrl: "/images/v3/cards/concept-spending-limits.png",
      horizontalImgUrl: "/images/v3/cards/concept-spending-limits-horizontal.png"
    },
    "request:templates/recipients-and-contracts": {
      title: "Known recipients and app contracts",
      description: "Cover transfers to known addresses and your contract's calls.",
      imgUrl: "/images/v3/cards/concept-recipient-allowlists.png",
      horizontalImgUrl: "/images/v3/cards/concept-recipient-allowlists-horizontal.png"
    },
    "request:templates/cosigner-approval": {
      title: "Co-signer approval",
      description: "Send large transfers to a co-signer for approval.",
      imgUrl: "/images/v3/cards/portal-conditional-approval-case.png",
      horizontalImgUrl: "/images/v3/cards/portal-conditional-approval-case-horizontal.png"
    },
    "request:templates/adjustable-limits": {
      title: "User-adjustable limits",
      description: "Let each user choose their own spending limits.",
      imgUrl: "/images/v3/cards/portal-user-policy-limits.png",
      horizontalImgUrl: "/images/v3/cards/portal-user-policy-limits-horizontal.png"
    },
    "request:templates/message-signing": {
      title: "Message signing",
      description: "Sign messages from users' wallets without a prompt.",
      imgUrl: "/images/v3/cards/concept-signing-actions.png",
      horizontalImgUrl: "/images/v3/cards/concept-signing-actions-horizontal.png"
    }
  };
  const entryFor = slug => registry[slug] || registry[`${ownership}:${slug}`];
  const hrefFor = slug => {
    if (slug === 'overview') return basePath;
    const overrides = hrefOverrides[basePath] || ({});
    return overrides[slug] || `${basePath}/${slug}`;
  };
  return <CardGroup cols={cols}>
      {pages.map(slug => {
    const entry = entryFor(slug);
    return <ImageCard key={slug} title={entry.title} description={entry.description} href={hrefFor(slug)} imgUrl={horizontal ? entry.horizontalImgUrl : entry.imgUrl} horizontal={horizontal} />;
  })}
    </CardGroup>;
};

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>;
};

Your React Native app does not change policies, and a policy change needs no new app release. You manage Request policies in the Developer Portal or with the CLI, and your app handles what each new version means for users: consent again, and confirm any personal limits.

Change a live policy without losing track of what Para enforces. This page covers versions, the operations that move a policy between states, time windows, and what each change does to consents, personal limits and open approvals.

<Info>
  Policy changes happen on a **Development** key, in Para's beta environment. Production keys are read-only: a policy reaches production only through a production review, and later changes on beta do not update production until Para reviews them again.
</Info>

## Policy IDs, versions and records

Three identifiers describe a policy. What users and approvers save follows the policy ID and version, while lifecycle commands act on a record.

| Identifier | Example | What it identifies |
| - | - | - |
| Policy ID | `"id": "treasury-guardrail"` | The policy across all its versions. It never changes. |
| Version | `"version": "3"` | One definition of the policy. Each edit uses the next number. |
| Record ID | `policy-record:abc123` | One saved version. The CLI's `replace`, `activate`, `deactivate` and `archive` commands take it. |

Each record has a status. The Developer Portal shows the current version of each policy with its own labels:

| Record status | Meaning | Developer Portal |
| - | - | - |
| `draft` | Saved but not enforced. A key holds one Guardrail draft and one Request draft at a time. | **Draft** |
| `active` | Enforced while inside its time window. | **Active**, and **Expired** once its window ends |
| `inactive` | Turned off. It cannot be activated again. | **Off** |
| `superseded` | Replaced by a newer version. | Not listed |
| `archived` | Retired. | Not listed |

## Lifecycle operations

The Developer Portal and the CLI run the same operations on the same records.

| Operation | Developer Portal | CLI | Result |
| - | - | - | - |
| Create | **Save Draft** | `create` | A new draft. It does not affect signing. |
| Activate | **Activate** on a **Draft** row | `activate <record-id>` | Works only while the draft's time window is open. The draft becomes active, supersedes the active version with the same policy ID and turns on Permissions for the key. |
| Edit | **New Guardrail** or **New Request** with the same networks, then **Save changes** | `replace <record-id>` | An active policy is replaced at once and the old record becomes `superseded`. A draft is replaced by a new draft and the old one is archived. |
| Turn off | The switch on an **Active** row | `deactivate <record-id>` | The record becomes `inactive`. |
| Turn back on | The switch on an **Off** row | `create` the next version, `activate` it, then `archive` the old record | The policy is active again. From the CLI it is the next version. From the Portal it is a new policy, as described below. |
| Delete | Expand a **Draft** or **Off** row, select **Delete permission**, then **Delete** | `archive <record-id>` | The record becomes `archived`. Active records must be turned off first. |

Turning an `Off` policy back on in the Developer Portal does not create the next version. The Portal publishes a copy under a new policy ID, `dev-portal-permission-<uuid>`, at version `1`, activates it and archives the `Off` record. Update any policy ID your app stores, such as for usage reads or consent links, and on a Request, each user has to consent to the copy. The switch is unavailable, with the reason in its tooltip, while a draft of the same type exists, while another policy of the same type is active on the same networks, and once the policy's time window has ended. Activate or delete that draft, turn off the other policy, or create a new policy with a current window.

An edit keeps the policy ID, the exact selector and the product mode, and uses the next version. A different selector makes a separate policy, so in the Portal, changing the networks while editing saves a new draft instead of a new version.

The Portal has no **Edit** button on a row. Start **New Guardrail** or **New Request** and pick the same networks as the policy you want to change. The title becomes **Edit Guardrail Policy** or **Edit Request Policy**, and a notice reads **A policy already exists for this network selection. Continue to load its rules and conditions for editing.**

<Frame>
  <img src="https://mintcdn.com/getpara/og2N3eReFZeUrwV7/images/v3/permissions/developer-portal-edit-existing-policy.png?fit=max&auto=format&n=og2N3eReFZeUrwV7&q=85&s=f4f1174d8d5f46df7e28a0e9470d5c0b" alt="Developer Portal form titled Edit Guardrail Policy with the notice that a policy already exists for this network selection" width="1712" height="1112" data-path="images/v3/permissions/developer-portal-edit-existing-policy.png" />
</Frame>

<Warning>
  Turning off one policy removes only that policy's rules. Any other active policy keeps applying.

  Permissions enforces policies for a key only while at least one active policy is inside its time window. When the last one is turned off or its window ends, Para stops checking policies on REST and SDK signing. App-owned wallets then sign without checks. User-owned wallets show the same review pop-ups as a key without Permissions, such as those the **Transaction Pop-ups** setting turns on.

  The Developer Portal does not warn you before you turn off the last active policy. Afterwards it shows the notice **No active policies, signing is unrestricted. Draft and Off permissions do not affect signing.** The notice means no policy is checked; user-owned wallets can still show transaction review pop-ups, as described above.
</Warning>

### Confirm changes that affect open approvals

Replacing, activating or turning off a policy can invalidate open approval cases.

An approval case is open while it is `pending`, and after it completes until it is used to sign or expires. Para refuses a change that would invalidate open cases until you confirm it. A confirmed change ends those cases as `invalidated`, and their requests need a new attempt.

| Tool | What you see | To apply the change |
| - | - | - |
| Developer Portal | A dialog, **Invalidate open approval cases?**, with the number of affected cases. **Cancel** leaves everything as it was. | **Save anyway**, **Activate anyway**, **Turn off anyway** or **Turn on anyway** |
| CLI | A prompt with the number of affected cases. With `--yes`, the command prints the error message and exits with code `10`. With `--json`, the printed error also has `errorCode` `PERMISSIONS_V2_APPROVAL_CASES_AFFECTED` and the case IDs in `details.affectedCaseIds`. | `--force` on `replace`, `activate` or `deactivate` |

## Time windows

A time window limits when an active policy applies. It is not part of the policy JSON: set it under **Policy Time Window (Optional)** with **From** and **To** in the Portal, or with `--effective-from` and `--effective-until` in the CLI.

* Times are UTC, and the window includes both ends. The end must be later than the start.
* A future start date leaves the policy a draft until someone activates it at or after that time. Activating it earlier, or after its window has ended, fails with `policy_record_activation_window_inactive`, so to start a policy at a set time, run the activation then, for example from a scheduled CI job.
* An active policy shows as **Expired** once its window ends, and it no longer applies to signing.
* An edit keeps the current window unless you set a new one. Editing an active policy needs both the current and the new window to be open, so to change an expired policy, create the next version with a current window and activate it.

A time window is different from the `window` on an `aggregate` condition, which sets the period a spending limit measures.

## How changes affect users and approvals

A new version is a new definition. Anything bound to the previous version has to catch up:

| What depends on the version | What happens after an edit or turn back on |
| - | - |
| User consent to a Request | Consent covers one exact version. Each user consents again before the new version's rules apply to them. The consent screen looks the same as the first time and does not say the policy changed, so tell users in your app. |
| Personal limits | Saved limits return `status: "needs_confirmation"` with `previousValues`. The user saves or resets them for the new version. After a Portal turn back on, the policy has a new ID, so saved limits do not carry over and users start from the defaults. |
| Adjustable approval settings | Settings belong to one version. The new version starts from its published values, so save the settings again. |
| Open approval cases | Cases that have not been used to sign become `invalidated`, after you confirm the change. A signature collection ends only when the change replaces or turns off its own policy. |

<Warning>
  An approval case that has not been used to sign is invalidated when the active policies that apply to its request change, for example when a policy is replaced, activated or turned off. An invalidated case cannot complete, so start a new request to open a new case.
</Warning>

Before you confirm, look up the cases from your backend with `getPartnerApprovalCase` for each ID in the CLI's `details.affectedCaseIds`, and let them finish if you can.

## Run lifecycle changes from CI

The CLI runs without prompts in CI. Pin the environment, pass the key ID, add `--json` to read results and `--yes` to the commands that confirm:

```bash theme={null}
RECORD_ID=$(para keys permissions create "$KEY_ID" --file guardrail.json --environment beta --json | jq -r '.policy.recordId')
para keys permissions activate "$RECORD_ID" "$KEY_ID" --environment beta --yes --json
```

`replace`, `activate`, `deactivate` and `archive` require `--yes` when you pass `--json`. Writes work only with beta keys. A change that would invalidate open approval cases fails with exit code `10` in CI, so add `--force` only to steps that are meant to invalidate them.

<CardGroup cols={1}>
  <Card imgUrl="/images/v3/cards/cli-policies-horizontal.png" title="Permissions in the CLI" description="Every command, flag, exit code and the full CI setup." href="/v3/cli/permissions" horizontal />
</CardGroup>

When the new version behaves as intended on beta, follow the production steps for your platform to publish it. Production does not pick up beta changes on its own.

<PermissionsCards basePath="/v3/react-native/guides/permissions" pages={["production"]} cols={1} horizontal />

<PermissionsCards basePath="/v3/react-native/guides/permissions" pages={["testing", "adjustable-parameters"]} cols={2} />


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