> ## 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 permissions from your backend

> Assign approvers, bind wallets, change approval settings and read Permissions state from your server

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

Administration uses the REST SDK, `@getpara/rest-sdk`, alongside the server SDK. `ParaServer` signs with your wallets, while `ParaRestClient` with your secret API key runs the calls on this page.

Run Permissions administration from your backend with the REST SDK and your secret API key. This page covers assigning approvers, binding wallets to an authorization scope, changing adjustable approval settings, and reading policy state and history for support and audits.

<Info>
  Your backend cannot change policies themselves: those go through the Developer Portal or the CLI on a **Development** key, and reach production through a production review. Assignments, wallet bindings and approval settings belong to the environment you write them in, so set them up again with your production secret key after the policy is published there.
</Info>

## Connect

Create one `ParaRestClient` with your secret API key and keep it on the server.

```ts theme={null}
import { ParaRestClient } from "@getpara/rest-sdk";

const para = new ParaRestClient({
  apiKey: process.env.PARA_SECRET_KEY!,
  env: "BETA",
});
```

`env` defaults to `"PROD"`, so set `"BETA"` while you test. Every Permissions method accepts an optional last argument, `{ idempotencyKey, signal }`.

<Note>
  Conditional approvals and personal limits are turned on for each app by Para. Until they are, the calls on this page that use them return `403` with `PERMISSIONS_V2_EXTENSION_UNAVAILABLE`.
</Note>

## Assign roles and attributes

An authorization scope is an ID you choose that groups approvers, wallets and approval settings, such as one scope per customer organization. Assignments give a Para user roles and attributes in a scope, and the policy's eligibility rules decide what those assignments allow.

```ts theme={null}
await para.replaceUserApprovalAssignments("treasury", userId, {
  expectedRevision: 0,
  assignments: [
    { kind: "role", name: "finance" },
    { kind: "attribute", name: "region", value: "emea" },
  ],
});
```

* The call replaces the user's whole set. To change it later, read it with `getUserApprovalAssignments(scopeId, userId)` and send the set you want with its `revision`. Send `assignments: []` to remove them all.
* Use `expectedRevision: 0` for a user's first set. A stale revision returns `409` with `authority assignments changed; read the current revision and retry`.
* Each name must be declared by an active policy, so activate the policy first.
* A user can hold at most 25 assignments across all scopes in your app. More returns `400` with `a user may have at most 25 assignments in one partner project`, so plan for this if one person approves for many scopes.
* The user must have a wallet connected to your app. Assigning roles to anyone else returns `400` with `user is not linked to this app`. Removing a user's assignments works either way.

`listAuthorizationScopeMembers(scopeId, { limit, afterUserId })` lists the users who hold assignments in a scope.

## Bind wallets to the scope

A wallet's binding tells Para which scope supplies its approvers and settings. A wallet that matches an approval rule without a binding is refused with `403`.

```ts theme={null}
await para.setWalletAuthorizationScope(walletId, {
  expectedRevision: 0,
  authorizationScopeId: "treasury",
});
```

Read the binding with `getWalletAuthorizationScope(walletId)`, and pass its `revision` to move the wallet to another scope. The wallet must belong to your app; a wallet from another app returns `403`.

Moving a wallet to another scope invalidates its open approval cases with `authorization_scope_changed`, so Para asks you to confirm the move first, as described below.

## Configure adjustable approval settings

When a requirement declares adjustable approval settings, your backend can change them for a scope without a new policy version. Find the requirements, then read and update one:

```ts theme={null}
const { requirements } = await para.listConfigurableApprovalRequirements("treasury");
const [requirement] = requirements;

const reference = {
  authorizationScopeId: "treasury",
  policyId: requirement.policyId,
  policyVersion: requirement.policyVersion,
  requirementId: requirement.requirementId,
};

const { configuration } = await para.getEffectiveApprovalConfiguration(reference);

await para.updateApprovalConfiguration(reference, {
  expectedRevision: configuration?.revision ?? 0,
  values: { "finance-approvals": 3 },
});
```

`resetApprovalConfiguration(reference, { expectedRevision })` returns the requirement to its published values. Saving or resetting invalidates open cases for the requirement in that scope. The adjustable parameters page covers the setting kinds and their rules.

## Confirm changes that affect open approvals

`replaceUserApprovalAssignments`, `setWalletAuthorizationScope`, `updateApprovalConfiguration` and `resetApprovalConfiguration` check for open approval cases before they write.

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

  Through the API, a refused change returns `409` with code `APPROVAL_CASES_AFFECTED` and the case IDs in `affectedCaseIds`, and nothing changes. To confirm it, send the same change again with `force: true`.

  In the REST SDK, the error is a `ParaRestError` with that `code`, and `ParaRestError.affectedCaseIds` lists the cases.
</Note>

```ts theme={null}
import { ParaRestError } from "@getpara/rest-sdk";

const binding = await para.getWalletAuthorizationScope(walletId);
const move = { expectedRevision: binding.revision, authorizationScopeId: "payroll" };

try {
  await para.setWalletAuthorizationScope(walletId, move);
} catch (error) {
  if (!(error instanceof ParaRestError) || error.code !== "APPROVAL_CASES_AFFECTED") throw error;

  if (await confirmInvalidation(error.affectedCaseIds ?? [])) {
    await para.setWalletAuthorizationScope(walletId, { ...move, force: true });
  }
}
```

Look up the invalidated cases with `getPartnerApprovalCase` to tell the people waiting on them.

## Cancel a pending case

Your backend can withdraw any pending case in your app, for example when the payment it covers is no longer needed:

```ts theme={null}
const cancelled = await para.cancelPartnerApprovalCase(caseId);
```

Only a `pending` case can be cancelled. The case ends with status `cancelled` and `terminalReason` `case_cancelled`. When a pending transaction from a user's wallet is waiting on the case, Para discards it, so that request never signs. A request that should still go ahead needs a new attempt, which opens a new case.

Cancelling a case that is no longer pending returns `409` with `conditional approval case is not pending`. A signing call that is still waiting on the discarded transaction throws `TransactionReviewDenied`.

## Read state without acting for the user

Your backend can read what users and approvers have done, but it cannot act for them. It cannot grant consent, change a user's personal limits, or approve or reject a case.

| To read | REST SDK methods |
| - | - |
| Policies and their versions | `listPartnerPolicies`, `getPartnerPolicy`, `listPartnerPolicyVersions` |
| A user's consent for a wallet | `listUserPolicyConsents(userId, { walletId })`, `getUserPolicyConsent(userId, policyId, { walletId })` |
| A user's personal limits | `listUserAdjustablePolicies(userId)`, `getUserPolicyParameters(userId, policyId)` |
| Approval settings | `getConfigurableApprovalRequirement`, `getApprovalConfiguration`, `getEffectiveApprovalConfiguration` |
| Approval cases | `listPartnerApprovalCases({ limit, beforeCaseId })`, `getPartnerApprovalCase(caseId)` |
| Spending against a limit | `getPartnerPolicyUsage(policyId, { walletId, userId, chainId, tokenAddress, safeAddress })` |

User reads need the user to have a wallet connected to your app; otherwise they return `403` with `user has no wallet connection to the authenticated partner`. Case reads return the same fields approvers see, except `decision`.

`getPartnerPolicyUsage` needs `chainId`: a decimal chain ID for EVM, or the network form for other wallet types, such as `solana:mainnet`. Meters for Safe spending limits appear only when you also pass the Safe's address as `safeAddress`. The result is a snapshot of committed and reserved spending, not a preview of a future request.

## Histories

Assignments, wallet bindings, approval settings and personal limits keep their earlier revisions, and policies keep their versions.

| History | Method |
| - | - |
| Approval assignments | `listUserApprovalAssignmentHistory(scopeId, userId)` |
| Wallet scope bindings | `listWalletAuthorizationScopeHistory(walletId, { beforeRevision })` |
| Approval settings | `listApprovalConfigurationHistory(reference)` |
| Personal limits | `listUserPolicyParameterHistory(userId, policyId, { beforeRevision })` |
| Policy versions | `listPartnerPolicyVersions(policyId)` |

<CardGroup cols={2}>
  <Card imgUrl="/images/v3/cards/sdk-rest-permissions.png" title="Permissions REST reference" description="Every REST SDK method with its route, request and response." href="/v3/references/rest/permissions" />

  <Card imgUrl="/images/v3/cards/concept-error-handling.png" title="Permissions errors" description="Codes and messages for refused writes and reads." href="/v3/references/permissions/errors" />
</CardGroup>

<PermissionsCards basePath="/v3/server/guides/permissions" pages={["conditional-approvals", "adjustable-parameters", "lifecycle"]} cols={3} />

<PermissionsCards basePath="/v3/server/guides/permissions" pages={["overview"]} cols={1} horizontal />


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