# Install an app (consent)

> The app_installations.create operation: POST /api/v1/app_installations in the REST API and the app_installations_create tool in the MCP server.

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.

## At a glance

- **REST:** `POST /api/v1/app_installations`
- **MCP tool:** `app_installations_create`
- **Operation ID:** `app_installations.create`
- **Scope:** Organization
- **Permission:** `agents:admin`
- **Risk tier:** `security`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `agents:admin` scope
- **Since:** M10

## Request

### Body

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

| Field               | Type                                                                                               | Required | Description |
| ------------------- | -------------------------------------------------------------------------------------------------- | -------- | ----------- |
| `clientId`          | `ocl_…` ID                                                                                         | Yes      |             |
| `grantId`           | `ogr_…` ID                                                                                         | Yes      |             |
| `scopes`            | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` (all in the OpenAPI document) | Yes      |             |
| `limitTo`           | object                                                                                             | No       |             |
| `limitTo.entityIds` | array of `ent_…` ID                                                                                | No       |             |
| `limitTo.brandIds`  | array of `brd_…` ID                                                                                | No       |             |

## Response

`201 Created` with a JSON object:

| Field                               | Type                                                                                               | Description |
| ----------------------------------- | -------------------------------------------------------------------------------------------------- | ----------- |
| `id`                                | `ains_…` ID                                                                                        |             |
| `object`                            | `"app_installation"`                                                                               |             |
| `orgId`                             | `org_…` ID                                                                                         |             |
| `app`                               | object                                                                                             |             |
| `app.id`                            | `ocl_…` ID                                                                                         |             |
| `app.name`                          | string                                                                                             |             |
| `app.publisher`                     | string                                                                                             |             |
| `app.logoUrl`                       | string or null                                                                                     |             |
| `app.verified`                      | boolean                                                                                            |             |
| `app.status`                        | one of `active`, `disabled`                                                                        |             |
| `app.suspension`                    | object or null                                                                                     |             |
| `installedBy`                       | `usr_…` ID                                                                                         |             |
| `scopes`                            | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` (all in the OpenAPI document) |             |
| `limitTo`                           | object                                                                                             |             |
| `limitTo.entityIds`                 | array of `ent_…` ID                                                                                |             |
| `limitTo.brandIds`                  | array of `brd_…` ID                                                                                |             |
| `limits`                            | object                                                                                             |             |
| `limits.rps`                        | integer or null                                                                                    |             |
| `limits.mutationsPerDay`            | integer or null                                                                                    |             |
| `limits.amountLimits`               | array of object                                                                                    |             |
| `limits.amountLimits[].operationId` | string                                                                                             |             |
| `limits.amountLimits[].perCall`     | money or null                                                                                      |             |
| `limits.amountLimits[].perDay`      | money or null                                                                                      |             |
| `status`                            | one of `active`, `paused`, `revoked`                                                               |             |
| `pausedReason`                      | one of `manual`, `installer_lost_access`, `platform_suspended`, `app_disabled` or null             |             |
| `pausedAt`                          | string or null                                                                                     |             |
| `revokedAt`                         | string or null                                                                                     |             |
| `createdAt`                         | string                                                                                             |             |
| `updatedAt`                         | string                                                                                             |             |

## Examples

### Install for one legal entity

```http
POST /api/v1/app_installations
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "clientId": "ocl_01m4ttwd3veh9apbewxheq5xha",
  "grantId": "ogr_01m4ttwd3veh9apbewxheq5xhd",
  "scopes": [
    "filings:read",
    "filings:write",
    "tasks:read",
    "tasks:write"
  ],
  "limitTo": {
    "entityIds": [
      "ent_01m4ttwd3veh9apbewxheq5xh4"
    ]
  }
}
```

Response (`201`):

```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"
}
```

## Errors

| Code                                                                                               | HTTP status | When                                                                                                    | What to do                                                                                                                                                                                                                                                                                |
| -------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)             | 400         | A scope isn't one the app registered, or a field is invalid.                                            | Fix the fields listed in `details.issues` and send the request again.                                                                                                                                                                                                                     |
| [`app_not_installable`](https://ownrecord.io/docs/reference/errors.md#app_not_installable)         | 400         | The app is private to another organization, or listed but not (yet) approved in platform review.        | Ask the app's publisher to list it: private apps install only in the organization that registered them, and other organizations can install a listed app once platform review has approved it.                                                                                            |
| [`app_scopes_not_approved`](https://ownrecord.io/docs/reference/errors.md#app_scopes_not_approved) | 403         | Another organization's listed app: a scope its approved listing doesn't cover (it waits for re-review). | Consent only to the scopes platform review approved (`details.approved`). The others wait for the publisher's re-review; they can be added after it is approved.                                                                                                                          |
| [`oauth_client_disabled`](https://ownrecord.io/docs/reference/errors.md#oauth_client_disabled)     | 409         | The app is disabled.                                                                                    | Register a new app, or create a new agent.                                                                                                                                                                                                                                                |
| [`app_suspended`](https://ownrecord.io/docs/reference/errors.md#app_suspended)                     | 409         | Ownrecord suspended the app: nothing installs it while the suspension lasts.                            | The app can't be installed, resumed or authorized while the suspension lasts. Its organization's owners and admins, and those of every organization that installed it, were emailed the reason. Once Ownrecord reinstates it, resume its installation under Organization → Agents & apps. |
| [`oauth_grant_mismatch`](https://ownrecord.io/docs/reference/errors.md#oauth_grant_mismatch)       | 400         | The grant isn't yours or isn't the app's.                                                               | Start the authorization again from the application.                                                                                                                                                                                                                                       |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)           | 404         | The app, or an entity or brand in limitTo, doesn't exist.                                               | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                                                                                      |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)             | 403         | You can't install apps, or this isn't a signed-in person.                                               | 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), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`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).
