# Update the approval policy

> The approval_policies.update operation: PATCH /api/v1/approval_policy in the REST API and the approval_policies_update tool in the MCP server.

Changes the organization's approval policy. Each part you send replaces the current one; leave one out to
keep it.

- `tiers`: what agents, apps and MCP clients may do per risk tier: `allow`, `require_approval` or `deny`
  (a denied call fails with `approval_policy_denied`). Compliance and security are `require_approval` or
  `deny`, never `allow`: those calls always need a person.
- `twoPersonFor`: operations that need a second person's approval, whoever calls. For a call by an agent or
  app, two people approve.
- `amountLimits`: for operations that carry an amount, the amount per currency at or below which a call runs
  at once (micros as a string). Above it, in a currency without a limit, or without an amount, an agent's or
  app's call needs approval; for people the limits apply to the operations in `twoPersonFor`.

Only changes inside the organization that support dry runs can be named (`availableOperations` in
`approval_policies.get`), never the approvals operations themselves. Overrides for single entities, agents
and apps are set with `approval_policies.overrides.set`. Requests already waiting for approval keep their
requirement. Owners and admins only, with a sign-in in the last 15 minutes.

## At a glance

- **REST:** `PATCH /api/v1/approval_policy`
- **MCP tool:** `approval_policies_update`
- **Operation ID:** `approval_policies.update`
- **Scope:** Organization
- **Permission:** `approvals:admin`
- **Risk tier:** `security`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` optional
- **Modes:** Test and live
- **Authentication:** API key with the `approvals:admin` scope
- **Fresh sign-in:** In the console, a sign-in within the last 15 minutes
- **Since:** M2

## Request

### Body

A JSON object (`Content-Type: application/json`):

| Field                        | Type                                       | Required | Description |
| ---------------------------- | ------------------------------------------ | -------- | ----------- |
| `tiers`                      | object                                     | No       |             |
| `tiers.write`                | one of `allow`, `require_approval`, `deny` | No       |             |
| `tiers.financial`            | one of `allow`, `require_approval`, `deny` | No       |             |
| `tiers.compliance`           | one of `allow`, `require_approval`, `deny` | No       |             |
| `tiers.security`             | one of `allow`, `require_approval`, `deny` | No       |             |
| `twoPersonFor`               | array of string                            | No       |             |
| `amountLimits`               | array of object                            | No       |             |
| `amountLimits[].operationId` | string                                     | Yes      |             |
| `amountLimits[].perCall`     | money                                      | Yes      |             |
| `version`                    | integer (at least 0)                       | No       |             |

## Response

`200 OK` with a JSON object:

| Field                                    | Type                                                  | Description |
| ---------------------------------------- | ----------------------------------------------------- | ----------- |
| `object`                                 | `"approval_policy"`                                   |             |
| `id`                                     | `apol_…` ID or null                                   |             |
| `orgId`                                  | `org_…` ID                                            |             |
| `tiers`                                  | object                                                |             |
| `tiers.write`                            | one of `allow`, `require_approval`, `deny`            |             |
| `tiers.financial`                        | one of `allow`, `require_approval`, `deny`            |             |
| `tiers.compliance`                       | one of `allow`, `require_approval`, `deny`            |             |
| `tiers.security`                         | one of `allow`, `require_approval`, `deny`            |             |
| `twoPersonFor`                           | array of string                                       |             |
| `amountLimits`                           | array of object                                       |             |
| `amountLimits[].operationId`             | string                                                |             |
| `amountLimits[].perCall`                 | money                                                 |             |
| `version`                                | integer (at least 0)                                  |             |
| `updatedAt`                              | date-time or null                                     |             |
| `overrides`                              | array of object                                       |             |
| `overrides[].id`                         | `apol_…` ID                                           |             |
| `overrides[].object`                     | `"approval_policy_override"`                          |             |
| `overrides[].entityId`                   | `ent_…` ID or null                                    |             |
| `overrides[].agentId`                    | `agt_…` ID or null                                    |             |
| `overrides[].clientId`                   | `ocl_…` ID or null                                    |             |
| `overrides[].tiers`                      | object                                                |             |
| `overrides[].tiers.write`                | one of `allow`, `require_approval`, `deny` or null    |             |
| `overrides[].tiers.financial`            | one of `allow`, `require_approval`, `deny` or null    |             |
| `overrides[].tiers.compliance`           | one of `allow`, `require_approval`, `deny` or null    |             |
| `overrides[].tiers.security`             | one of `allow`, `require_approval`, `deny` or null    |             |
| `overrides[].twoPersonFor`               | array of string                                       |             |
| `overrides[].amountLimits`               | array of object                                       |             |
| `overrides[].amountLimits[].operationId` | string                                                |             |
| `overrides[].amountLimits[].perCall`     | money                                                 |             |
| `overrides[].version`                    | integer (at least 1)                                  |             |
| `overrides[].updatedAt`                  | date-time                                             |             |
| `availableOperations`                    | array of object                                       |             |
| `availableOperations[].id`               | string                                                |             |
| `availableOperations[].summary`          | string                                                |             |
| `availableOperations[].riskTier`         | one of `write`, `financial`, `compliance`, `security` |             |
| `availableOperations[].scope`            | one of `org`, `entity`, `brand`                       |             |
| `availableOperations[].amounts`          | boolean                                               |             |

## Examples

### Require a second person for brand changes

```http
PATCH /api/v1/approval_policy
Authorization: Bearer or_test_…
Content-Type: application/json

{
  "twoPersonFor": [
    "brands.update"
  ],
  "version": 1
}
```

Response (`200`):

```json
{
  "object": "approval_policy",
  "id": "apol_01m4ttwd3veh9apbewxheq5xh9",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "tiers": {
    "write": "allow",
    "financial": "allow",
    "compliance": "require_approval",
    "security": "require_approval"
  },
  "twoPersonFor": ["brands.update"],
  "amountLimits": [],
  "version": 2,
  "updatedAt": "2026-10-13T14:30:00.000Z",
  "overrides": [],
  "availableOperations": [
    {
      "id": "brands.update",
      "summary": "Update a brand",
      "riskTier": "write",
      "scope": "brand",
      "amounts": false
    },
    {
      "id": "members.remove",
      "summary": "Remove a member",
      "riskTier": "security",
      "scope": "org",
      "amounts": false
    }
  ]
}
```

### Agents and apps may not change anything without approval

```http
PATCH /api/v1/approval_policy
Authorization: Bearer or_test_…
Content-Type: application/json

{
  "tiers": {
    "write": "require_approval",
    "financial": "require_approval"
  },
  "version": 2
}
```

Response (`200`):

```json
{
  "object": "approval_policy",
  "id": "apol_01m4ttwd3veh9apbewxheq5xh9",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "tiers": {
    "write": "require_approval",
    "financial": "require_approval",
    "compliance": "require_approval",
    "security": "require_approval"
  },
  "twoPersonFor": ["brands.update"],
  "amountLimits": [],
  "version": 3,
  "updatedAt": "2026-10-13T14:30:00.000Z",
  "overrides": [],
  "availableOperations": [
    {
      "id": "brands.update",
      "summary": "Update a brand",
      "riskTier": "write",
      "scope": "brand",
      "amounts": false
    },
    {
      "id": "members.remove",
      "summary": "Remove a member",
      "riskTier": "security",
      "scope": "org",
      "amounts": false
    }
  ]
}
```

### Turn the two-person rule off

```http
PATCH /api/v1/approval_policy
Authorization: Bearer or_test_…
Content-Type: application/json

{
  "twoPersonFor": [],
  "amountLimits": [],
  "version": 2
}
```

Response (`200`):

```json
{
  "object": "approval_policy",
  "id": "apol_01m4ttwd3veh9apbewxheq5xh9",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "tiers": {
    "write": "allow",
    "financial": "allow",
    "compliance": "require_approval",
    "security": "require_approval"
  },
  "twoPersonFor": [],
  "amountLimits": [],
  "version": 3,
  "updatedAt": "2026-10-13T14:30:00.000Z",
  "overrides": [],
  "availableOperations": [
    {
      "id": "brands.update",
      "summary": "Update a brand",
      "riskTier": "write",
      "scope": "brand",
      "amounts": false
    },
    {
      "id": "members.remove",
      "summary": "Remove a member",
      "riskTier": "security",
      "scope": "org",
      "amounts": false
    }
  ]
}
```

## Errors

| Code                                                                                       | HTTP status | When                                                                                                                                          | What to do                                                                      |
| ------------------------------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)     | 400         | The policy names an unknown or ineligible operation, sets compliance or security to allow, or its amount limits don't fit (`details.issues`). | Fix the fields listed in `details.issues` and send the request again.           |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)       | 409         | The policy changed since you read it (`version`); read it again.                                                                              | Fetch the object again and retry the change.                                    |
| [`fresh_auth_required`](https://ownrecord.io/docs/reference/errors.md#fresh_auth_required) | 403         | You last signed in more than 15 minutes ago.                                                                                                  | Sign in again, then repeat the action within 15 minutes.                        |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)     | 403         | Only owners and admins change the approval policy.                                                                                            | Ask an organization owner or admin for a role or scope that allows this action. |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
