# App installations tools

> MCP tools for App installations: app_installations_activity, app_installations_create, app_installations_create_agent, app_installations_get, app_installations_list, app_installations_pause, app_installations_reassign, app_installations_resume, app_installations_revoke, app_installations_update.

- `app_installations_activity`: List an installed app's activity
- `app_installations_create`: Install an app (consent)
- `app_installations_create_agent`: Ask for an agent the app owns
- `app_installations_get`: Get an installed app
- `app_installations_list`: List installed apps
- `app_installations_pause`: Pause an installed app
- `app_installations_reassign`: Re-assign an installed app
- `app_installations_resume`: Resume a paused app
- `app_installations_revoke`: Uninstall an app
- `app_installations_update`: Narrow an installed app's access

## `app_installations_activity`

**List an installed app's activity.** Operation [`app_installations.activity`](https://ownrecord.io/docs/reference/app-installations-activity.md) · Organization level · permission `audit:read` · risk tier `read` · since M10

The installed app's activity feed in this organization, newest first, from the audit log: every change it made
(`outcome: "succeeded"`), every call that asked for approval instead of running (`approval_requested`, with the
approval's current `status`), and every run a person approved (`ran_after_approval`), each with the operation,
its target, the mode, the entity or brand, the surface (`via`), the app's OAuth client and the installer the
call was attributed to (`onBehalfOfUserId`). Reads aren't in the audit log, so they aren't listed. Both modes
are listed; `usage` is today's use (UTC) of its daily limits in the request's mode. Paginate with `limit`
(1–100, default 10) and `starting_after`.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type          | Required | Description |
| ---------------- | ------------- | -------- | ----------- |
| `id`             | ID (`ains_…`) | yes      | –           |
| `limit`          | integer       | no       | (1–100)     |
| `starting_after` | ID (`aud_…`)  | no       | –           |

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): `limit` or `starting_after` is invalid.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't read the organization's audit log.

### Example

**A refund above the daily limit, approved and run**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc"
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "object": "activity_entry",
      "livemode": false,
      "entityId": "ent_01m4ttwd3veh9apbewxheq5xh4",
      "brandId": "brd_01m4ttwd3veh9apbewxheq5xh5",
      "via": "mcp",
      "clientId": "ocl_01m4ttwd3veh9apbewxheq5xha",
      "onBehalfOfUserId": "usr_01m4ttwd3veh9apbewxheq5xh6",
      "appInstallationId": null,
      "id": "aud_01m4ttwd3veh9apbewxheq5xhh",
      "at": "2026-10-26T15:02:11.000Z",
      "action": "refunds.create",
      "operationId": "refunds.create",
      "target": "re_01m4ttwd3veh9apbewxheq5xhj",
      "outcome": "ran_after_approval",
      "approval": {
        "id": "apr_01m4ttwd3veh9apbewxheq5xhk",
        "status": "executed",
        "approvedBy": ["usr_01m4ttwd3veh9apbewxheq5xh7"]
      }
    },
    {
      "object": "activity_entry",
      "livemode": false,
      "entityId": "ent_01m4ttwd3veh9apbewxheq5xh4",
      "brandId": "brd_01m4ttwd3veh9apbewxheq5xh5",
      "via": "mcp",
      "clientId": "ocl_01m4ttwd3veh9apbewxheq5xha",
      "onBehalfOfUserId": "usr_01m4ttwd3veh9apbewxheq5xh6",
      "appInstallationId": null,
      "id": "aud_01m4ttwd3veh9apbewxheq5xhf",
      "at": "2026-10-26T14:30:00.000Z",
      "action": "approvals.request",
      "operationId": "refunds.create",
      "target": "apr_01m4ttwd3veh9apbewxheq5xhk",
      "outcome": "approval_requested",
      "approval": {
        "id": "apr_01m4ttwd3veh9apbewxheq5xhk",
        "status": "executed",
        "approvedBy": ["usr_01m4ttwd3veh9apbewxheq5xh7"]
      }
    },
    {
      "object": "activity_entry",
      "livemode": false,
      "entityId": "ent_01m4ttwd3veh9apbewxheq5xh4",
      "brandId": "brd_01m4ttwd3veh9apbewxheq5xh5",
      "via": "mcp",
      "clientId": "ocl_01m4ttwd3veh9apbewxheq5xha",
      "onBehalfOfUserId": "usr_01m4ttwd3veh9apbewxheq5xh6",
      "appInstallationId": null,
      "id": "aud_01m4ttwd3veh9apbewxheq5xhg",
      "at": "2026-10-26T14:10:00.000Z",
      "action": "refunds.create",
      "operationId": "refunds.create",
      "target": "re_01m4ttwd3veh9apbewxheq5xhm",
      "outcome": "succeeded",
      "approval": null
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "usage": {
    "day": "2026-10-26",
    "livemode": false,
    "mutations": 2,
    "amounts": [
      {
        "operationId": "refunds.create",
        "amount": {
          "amount": "450000000",
          "currency": "EUR"
        }
      }
    ]
  }
}
```

## `app_installations_create`

**Install an app (consent).** Operation [`app_installations.create`](https://ownrecord.io/docs/reference/app-installations-create.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Installs a partner app in the organization with the scopes you consent to, optionally limited to some legal
entities or brands (`limitTo`; none = the whole organization). The consent screen calls it when an
organization owner or admin approves an app's authorization request; `grantId` is that request's grant.

The app then acts in the organization as the installation, attributed to you, with at most your own access,
re-checked on every call. By installing it, the organization instructs Ownrecord to disclose the data in these
scopes to the app's publisher. Installing an app that is already installed updates its scopes and `limitTo`
and makes you its installer. Private apps install only in the organization that registered them.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type                                                                 | Required | Description                                                                                                                                                                                              |
| ----------------- | -------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clientId`        | ID (`ocl_…`)                                                         | yes      | –                                                                                                                                                                                                        |
| `grantId`         | ID (`ogr_…`)                                                         | yes      | –                                                                                                                                                                                                        |
| `scopes`          | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` | yes      | (1–200 items)                                                                                                                                                                                            |
| `limitTo`         | object                                                               | no       | –                                                                                                                                                                                                        |
| `idempotency_key` | string                                                               | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                                                              | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A scope isn't one the app registered, or a field is invalid.
- [`app_not_installable`](https://ownrecord.io/docs/reference/errors.md#app_not_installable): The app is private to another organization, or listed but not (yet) approved in platform review.
- [`app_scopes_not_approved`](https://ownrecord.io/docs/reference/errors.md#app_scopes_not_approved): Another organization's listed app: a scope its approved listing doesn't cover (it waits for re-review).
- [`oauth_client_disabled`](https://ownrecord.io/docs/reference/errors.md#oauth_client_disabled): The app is disabled.
- [`app_suspended`](https://ownrecord.io/docs/reference/errors.md#app_suspended): Ownrecord suspended the app: nothing installs it while the suspension lasts.
- [`oauth_grant_mismatch`](https://ownrecord.io/docs/reference/errors.md#oauth_grant_mismatch): The grant isn't yours or isn't the app's.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): The app, or an entity or brand in limitTo, doesn't exist.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't install apps, or this isn't a signed-in person.

### Example

**Install for one legal entity**

Arguments:

```json
{
  "clientId": "ocl_01m4ttwd3veh9apbewxheq5xha",
  "grantId": "ogr_01m4ttwd3veh9apbewxheq5xhd",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"]
  },
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-25T09:00:00.000Z"
}
```

## `app_installations_create_agent`

**Ask for an agent the app owns.** Operation [`app_installations.create_agent`](https://ownrecord.io/docs/reference/app-installations-create-agent.md) · Organization level · permission `agents:admin` · risk tier `security` · since M13

Called by an installed app with its own access token: asks for an **agent the app owns** in the organization,
for work that runs without the app's user session (for example a filing agent). It is a security operation, so
the call always answers `202` with an approval request: an owner or admin of the organization (not the person
who installed the app) approves it in the console, and the approved run creates the agent exactly as asked.
Poll the approval (`approvals.get`) or watch `approval.resolved`; its `resultRef` is the new agent's ID, and
`agents.get` returns its `clientId`.

The agent holds one `role` at a `scope` like a member, works in test mode unless `livemodeAccess` is asked for
and the installer may use live mode, and its token `scopes` stay within the scopes the app was granted (default:
everything the role can use among them). It authenticates with `private_key_jwt` only: give its public keys in
`jwks`. Its calls are attributed to the agent, the app's installation and the person who installed the app; it
stops when the app is paused or uninstalled, or the installer leaves the organization. `id` is your own
installation (`ains_…`); an installation limited to some entities or brands can't ask for agents.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: false` · `openWorldHint: false`

### Arguments

| Argument          | Type                                                                                            | Required | Description                                                                                                                                                                                              |
| ----------------- | ----------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`)                                                                                   | yes      | The calling app's installation in this organization.                                                                                                                                                     |
| `name`            | string                                                                                          | yes      | (1–100 characters)                                                                                                                                                                                       |
| `description`     | string                                                                                          | no       | (1–500 characters)                                                                                                                                                                                       |
| `contact`         | string                                                                                          | no       | Who to contact about the agent (default: none). (1–500 characters)                                                                                                                                       |
| `vendor`          | string                                                                                          | no       | Model or vendor description (default: the app's publisher). (1–500 characters)                                                                                                                           |
| `role`            | one of `org_admin`, `billing_admin`, `finance`, `tax_manager`, `support`, `developer`, `viewer` | yes      | –                                                                                                                                                                                                        |
| `scope`           | object or object or object                                                                      | no       | (default `{"type":"org"}`)                                                                                                                                                                               |
| `livemodeAccess`  | boolean                                                                                         | no       | (default `false`)                                                                                                                                                                                        |
| `scopes`          | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin`                            | no       | (1–200 items)                                                                                                                                                                                            |
| `jwks`            | object                                                                                          | yes      | The agent's public keys for private_key_jwt client authentication.                                                                                                                                       |
| `limits`          | object                                                                                          | no       | –                                                                                                                                                                                                        |
| `toolSets`        | array of string                                                                                 | no       | (at most 20 items)                                                                                                                                                                                       |
| `idempotency_key` | string                                                                                          | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                                                                                         | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required): Always for the app's own call: an owner or admin approves it in the console.
- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A field is invalid, e.g. a scope the app wasn't granted or the role can't use.
- [`agent_role_invalid`](https://ownrecord.io/docs/reference/errors.md#agent_role_invalid): The role is org_owner, or isn't allowed at the scope's type.
- [`agent_delegation_not_allowed`](https://ownrecord.io/docs/reference/errors.md#agent_delegation_not_allowed): The agent would get a permission (or live access) the app doesn't have at its scope.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): The installation isn't the calling app's, or the scope's entity or brand doesn't exist.
- [`app_installation_paused`](https://ownrecord.io/docs/reference/errors.md#app_installation_paused): The installation was paused before the approved run.
- [`oauth_client_disabled`](https://ownrecord.io/docs/reference/errors.md#oauth_client_disabled): The app was disabled before the approved run.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): The caller isn't an installed app (people create agents with agents.create), or lacks agents:admin.

### Example

**A filing agent for the whole organization (after approval)**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "name": "Ledgerly Filing Agent",
  "description": "Prepares and files the quarterly returns",
  "contact": "filings@ledgerly.example",
  "role": "finance",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "jwks": {
    "keys": [
      {
        "kty": "EC",
        "crv": "P-256",
        "kid": "ledgerly-filing-1",
        "x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU",
        "y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
      }
    ]
  },
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "agent": {
    "id": "agt_01m4ttwd3veh9apbewxheq5xh8",
    "object": "agent",
    "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
    "name": "Ledgerly Filing Agent",
    "description": "Prepares and files the quarterly returns",
    "contact": "filings@ledgerly.example",
    "vendor": "Ledgerly AI B.V.",
    "ownerType": "app",
    "appInstallationId": "ains_01m4ttwd3veh9apbewxheq5xhc",
    "role": "finance",
    "scope": {
      "type": "org"
    },
    "livemodeAccess": false,
    "limits": {
      "rps": null,
      "mutationsPerDay": null,
      "amountLimits": []
    },
    "toolSets": [],
    "status": "active",
    "clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
    "authMethod": "private_key_jwt",
    "secretPrefix": null,
    "dpopBoundAccessTokens": false,
    "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
    "createdBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
    "createdAt": "2026-10-25T09:00:00.000Z",
    "updatedAt": "2026-10-25T09:00:00.000Z",
    "pausedAt": null,
    "revokedAt": null
  },
  "credentials": {
    "clientId": "ocl_01m4ttwd3veh9apbewxheq5xh9",
    "clientSecret": null,
    "tokenEndpoint": "https://ownrecord.io/oauth/token",
    "authMethod": "private_key_jwt"
  }
}
```

## `app_installations_get`

**Get an installed app.** Operation [`app_installations.get`](https://ownrecord.io/docs/reference/app-installations-get.md) · Organization level · permission `agents:read` · risk tier `read` · since M10

Returns one app installation of the organization: the app, the installer, scopes, `limitTo` and status.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument | Type          | Required | Description |
| -------- | ------------- | -------- | ----------- |
| `id`     | ID (`ains_…`) | yes      | –           |

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't see the organization's agents and apps.

### Example

**An installation**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-25T09:00:00.000Z"
}
```

## `app_installations_list`

**List installed apps.** Operation [`app_installations.list`](https://ownrecord.io/docs/reference/app-installations-list.md) · Organization level · permission `agents:read` · risk tier `read` · since M10

Lists the apps installed in the organization, newest first: the app and its publisher (verified or not), who installed it, the scopes granted, `limitTo` and the status. Filter by `status`; paginate with `limit` and `starting_after`.

Annotations: `readOnlyHint: true` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument         | Type                                 | Required | Description |
| ---------------- | ------------------------------------ | -------- | ----------- |
| `limit`          | integer                              | no       | (1–100)     |
| `starting_after` | ID (`ains_…`)                        | no       | –           |
| `status`         | one of `active`, `paused`, `revoked` | no       | –           |

- **Read-only:** changes nothing; `idempotency_key` is ignored.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A filter or pagination value is invalid.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't see the organization's agents and apps.

### Examples

**Installed apps**

Arguments:

```json
{}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
      "object": "app_installation",
      "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
      "app": {
        "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
        "name": "Ledgerly AI",
        "publisher": "Ledgerly AI B.V.",
        "logoUrl": null,
        "verified": true,
        "status": "active",
        "suspension": null
      },
      "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
      "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
      "limitTo": {
        "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
        "brandIds": []
      },
      "limits": {
        "rps": null,
        "mutationsPerDay": null,
        "amountLimits": []
      },
      "status": "active",
      "pausedReason": null,
      "pausedAt": null,
      "revokedAt": null,
      "createdAt": "2026-10-25T09:00:00.000Z",
      "updatedAt": "2026-10-25T09:00:00.000Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

**Paused installations, one per page**

Arguments:

```json
{
  "status": "paused",
  "limit": 1
}
```

Result `output`:

```json
{
  "object": "list",
  "data": [
    {
      "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
      "object": "app_installation",
      "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
      "app": {
        "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
        "name": "Ledgerly AI",
        "publisher": "Ledgerly AI B.V.",
        "logoUrl": null,
        "verified": true,
        "status": "active",
        "suspension": null
      },
      "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
      "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
      "limitTo": {
        "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
        "brandIds": []
      },
      "limits": {
        "rps": null,
        "mutationsPerDay": null,
        "amountLimits": []
      },
      "status": "paused",
      "pausedReason": "installer_lost_access",
      "pausedAt": "2026-10-26T14:30:00.000Z",
      "revokedAt": null,
      "createdAt": "2026-10-25T09:00:00.000Z",
      "updatedAt": "2026-10-26T14:30:00.000Z"
    }
  ],
  "has_more": true,
  "next_cursor": "ains_01m4ttwd3veh9apbewxheq5xhc"
}
```

## `app_installations_pause`

**Pause an installed app.** Operation [`app_installations.pause`](https://ownrecord.io/docs/reference/app-installations-pause.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Pauses an installed app: **its access tokens and refresh tokens stop working in the organization at once**,
and it can't act there until resumed (`app_installations.resume`), after which it must ask for authorization
again. Pausing a paused app changes nothing (one paused because its installer left stays so, keeping its
tokens for after the re-assignment).

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type          | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`) | yes      | –                                                                                                                                                                                                        |
| `idempotency_key` | string        | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean       | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The installation is revoked.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The installation changed at the same moment; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't manage the organization's apps.

### Example

**Pause an app**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "paused",
  "pausedReason": "manual",
  "pausedAt": "2026-10-26T14:30:00.000Z",
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

## `app_installations_reassign`

**Re-assign an installed app.** Operation [`app_installations.reassign`](https://ownrecord.io/docs/reference/app-installations-reassign.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Makes another organization owner or admin the installer of an app: its calls are attributed to them and limited
to their access from now on. An installation paused because its installer left resumes, with the same tokens.
The new installer must be an `org_owner` or `org_admin` of the organization.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type          | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`) | yes      | –                                                                                                                                                                                                        |
| `userId`          | ID (`usr_…`)  | yes      | –                                                                                                                                                                                                        |
| `idempotency_key` | string        | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean       | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The installation is revoked.
- [`installer_not_eligible`](https://ownrecord.io/docs/reference/errors.md#installer_not_eligible): The person isn't an active org_owner or org_admin of the organization.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The installation changed at the same moment; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't manage the organization's apps.

### Example

**Re-assign after the installer left**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "userId": "usr_01m4ttwd3veh9apbewxheq5xh7",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh7",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

## `app_installations_resume`

**Resume a paused app.** Operation [`app_installations.resume`](https://ownrecord.io/docs/reference/app-installations-resume.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Resumes a paused installation. After a manual pause the app must ask for authorization again (its tokens
were revoked). An installation paused because its installer left can only resume once its installer has access
again: re-assign it instead (`app_installations.reassign`), which also resumes it. Resuming an active
installation changes nothing; a revoked one can't be resumed.

While Ownrecord suspends the app (`pausedReason: platform_suspended`), it can't be resumed (`app_suspended`).
After a reinstatement, resuming it is your decision; the app then asks for authorization again.

An app disabled by the organization that registered it (`pausedReason: app_disabled`) never resumes
(`oauth_client_disabled`): uninstall it.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type          | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`) | yes      | –                                                                                                                                                                                                        |
| `idempotency_key` | string        | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean       | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): The installation is revoked.
- [`oauth_client_disabled`](https://ownrecord.io/docs/reference/errors.md#oauth_client_disabled): The organization that registered the app disabled it for good: uninstall it.
- [`app_suspended`](https://ownrecord.io/docs/reference/errors.md#app_suspended): Ownrecord suspended the app: it can't be resumed until it is reinstated.
- [`installer_not_eligible`](https://ownrecord.io/docs/reference/errors.md#installer_not_eligible): The installer still has no access: re-assign the app first.
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The installation changed at the same moment; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't manage the organization's apps.

### Example

**Resume an app**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

## `app_installations_revoke`

**Uninstall an app.** Operation [`app_installations.revoke`](https://ownrecord.io/docs/reference/app-installations-revoke.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Uninstalls an app from the organization: its tokens stop working there at once and the installation is kept
as `revoked` with its audit trail. The app can be installed again later through the consent screen.
Uninstalling an uninstalled app changes nothing.

Annotations: `readOnlyHint: false` · `destructiveHint: true` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type          | Required | Description                                                                                                                                                                                              |
| ----------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`) | yes      | –                                                                                                                                                                                                        |
| `idempotency_key` | string        | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean       | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No installation with this ID exists in the organization.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The installation changed at the same moment; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't manage the organization's apps.

### Example

**Uninstall an app**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "revoked",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": "2026-10-26T14:30:00.000Z",
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

## `app_installations_update`

**Narrow an installed app's access.** Operation [`app_installations.update`](https://ownrecord.io/docs/reference/app-installations-update.md) · Organization level · permission `agents:admin` · risk tier `security` · since M10

Changes what an installed app may do in the organization: fewer `scopes` (only scopes it already has; more need
the app to ask for consent again), a different `limitTo` (entities or brands; empty lists = the whole
organization), or its `limits`. Changes apply to the app's next call. A revoked installation can't be changed.

`limits` work as an agent's (spec §41.4): `rps` operation calls per second (`agent_rate_limited`),
`mutationsPerDay` changes per UTC day and mode (`agent_daily_limit_reached`), and `amountLimits` per financial
operation and currency (`perCall`, `perDay`; money as integer micros in a string): a call above them, or
without an amount, creates an approval request instead of running. Send only what changes; `null` clears a
value.

Annotations: `readOnlyHint: false` · `destructiveHint: false` · `idempotentHint: true` · `openWorldHint: false`

### Arguments

| Argument          | Type                                                                 | Required | Description                                                                                                                                                                                              |
| ----------------- | -------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | ID (`ains_…`)                                                        | yes      | –                                                                                                                                                                                                        |
| `scopes`          | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` | no       | (1–200 items)                                                                                                                                                                                            |
| `limitTo`         | object                                                               | no       | –                                                                                                                                                                                                        |
| `limits`          | object                                                               | no       | –                                                                                                                                                                                                        |
| `idempotency_key` | string                                                               | no       | A unique string for this change. Retrying with the same key and arguments never acts twice. When left out, the server generates one and returns it in the result's `idempotency_key`. (1–255 characters) |
| `dry_run`         | boolean                                                              | no       | true: check permissions and rules and compute the result, then roll everything back. Nothing is saved. (default `false`)                                                                                 |

- **Idempotency:** the result returns the `idempotency_key` used, yours or one the server generated. Retry with it: a call with the same key and arguments never acts twice (keys are kept for 24 hours).
- **Dry run:** supported. `dry_run: true` checks permissions and rules and computes the result, then rolls everything back.

### Errors

- [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed): A scope isn't one the installation already has, or a limit can't work (e.g. an amount limit on an operation that moves no amount).
- [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found): No such installation, or an entity or brand in limitTo doesn't exist.
- [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict): The installation changed at the same moment; retry.
- [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied): You can't manage the organization's apps.

### Examples

**Read-only filings**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "scopes": ["filings:read", "tasks:read"],
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "tasks:read"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": null,
    "amountLimits": []
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

**Ten changes a day, refunds up to €100 a day**

Arguments:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "limits": {
    "mutationsPerDay": 10,
    "amountLimits": [
      {
        "operationId": "refunds.create",
        "perDay": {
          "amount": "100000000",
          "currency": "EUR"
        }
      }
    ]
  },
  "idempotency_key": "8d0c5f8e-4b7a-4e43-9b0f-2f6a0c1d9e21"
}
```

Result `output`:

```json
{
  "id": "ains_01m4ttwd3veh9apbewxheq5xhc",
  "object": "app_installation",
  "orgId": "org_01m4ttwd3veh9apbewxheq5xh3",
  "app": {
    "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
    "name": "Ledgerly AI",
    "publisher": "Ledgerly AI B.V.",
    "logoUrl": null,
    "verified": true,
    "status": "active",
    "suspension": null
  },
  "installedBy": "usr_01m4ttwd3veh9apbewxheq5xh6",
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write"],
  "limitTo": {
    "entityIds": ["ent_01m4ttwd3veh9apbewxheq5xh4"],
    "brandIds": []
  },
  "limits": {
    "rps": null,
    "mutationsPerDay": 10,
    "amountLimits": [
      {
        "operationId": "refunds.create",
        "perCall": null,
        "perDay": {
          "amount": "100000000",
          "currency": "EUR"
        }
      }
    ]
  },
  "status": "active",
  "pausedReason": null,
  "pausedAt": null,
  "revokedAt": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```
