# Update a registered app

> The apps.update operation: PATCH /api/v1/apps/{id} in the REST API and the apps_update tool in the MCP server.

Changes a registered app's name, publisher, links, redirect URIs, visibility, scopes or whether every token must
be DPoP-bound (`dpopBoundAccessTokens`). Send only what changes; `null` removes a link. Narrower scopes apply to
the app's tokens at once in every installation; wider scopes need the organizations' consent again.

For listed apps (see `apps.listing.get`): `visibility: "listed"` waits for an application
(`apps.listing.submit`); `"private"` ends the listing. On an approved app, scopes beyond the approved ones go to
review again (`pending`): the app stays listed for its approved scopes, other organizations can consent only to
those, and installations keep the scopes they consented to until the new ones are approved and consented. A new
name or publisher of an approved app is reviewed again before the app is listed again.

## At a glance

- **REST:** `PATCH /api/v1/apps/{id}`
- **MCP tool:** `apps_update`
- **Operation ID:** `apps.update`
- **Scope:** Organization
- **Permission:** `agents: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 `agents:admin` scope
- **Since:** M10

## Request

### Path parameters

| Field | Type       | Description |
| ----- | ---------- | ----------- |
| `id`  | `ocl_…` ID |             |

### Body

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

| Field                   | Type                                                                                               | Required | Description                                                                               |
| ----------------------- | -------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------- |
| `name`                  | string                                                                                             | No       |                                                                                           |
| `publisher`             | string                                                                                             | No       |                                                                                           |
| `logoUrl`               | string or null                                                                                     | No       |                                                                                           |
| `websiteUrl`            | string or null                                                                                     | No       |                                                                                           |
| `privacyPolicyUrl`      | string or null                                                                                     | No       |                                                                                           |
| `redirectUris`          | array of string                                                                                    | No       |                                                                                           |
| `visibility`            | one of `private`, `listed`                                                                         | No       |                                                                                           |
| `scopes`                | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` (all in the OpenAPI document) | No       |                                                                                           |
| `dpopBoundAccessTokens` | boolean                                                                                            | No       | Require DPoP for every token: a token request without a DPoP proof is refused (RFC 9449). |

## Response

`200 OK` with a JSON object:

| Field                   | Type                                                                                               | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------- | ----------- |
| `id`                    | `ocl_…` ID                                                                                         |             |
| `object`                | `"app"`                                                                                            |             |
| `ownerOrgId`            | `org_…` ID                                                                                         |             |
| `name`                  | string                                                                                             |             |
| `publisher`             | string                                                                                             |             |
| `logoUrl`               | string or null                                                                                     |             |
| `websiteUrl`            | string or null                                                                                     |             |
| `privacyPolicyUrl`      | string or null                                                                                     |             |
| `redirectUris`          | array of string                                                                                    |             |
| `type`                  | one of `confidential`, `public`                                                                    |             |
| `visibility`            | one of `private`, `listed`                                                                         |             |
| `review`                | object                                                                                             |             |
| `review.status`         | one of `not_submitted`, `pending`, `changes_requested`, `approved`, `rejected`                     |             |
| `review.at`             | string or null                                                                                     |             |
| `verified`              | boolean                                                                                            |             |
| `scopes`                | array of one of 84 values, e.g. `org:read`, `org:write`, `org:admin` (all in the OpenAPI document) |             |
| `secretPrefix`          | string or null                                                                                     |             |
| `dpopBoundAccessTokens` | boolean                                                                                            |             |
| `status`                | one of `active`, `disabled`                                                                        |             |
| `suspension`            | object or null                                                                                     |             |
| `createdAt`             | string                                                                                             |             |
| `updatedAt`             | string                                                                                             |             |

## Examples

### Add a redirect URI

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

{
  "redirectUris": [
    "https://ledgerly.example/oauth/callback",
    "https://eu.ledgerly.example/oauth/callback"
  ]
}
```

Response (`200`):

```json
{
  "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
  "object": "app",
  "ownerOrgId": "org_01m4ttwd3veh9apbewxheq5xhb",
  "name": "Ledgerly AI",
  "publisher": "Ledgerly AI B.V.",
  "logoUrl": null,
  "websiteUrl": "https://ledgerly.example",
  "privacyPolicyUrl": "https://ledgerly.example/privacy",
  "redirectUris": ["https://ledgerly.example/oauth/callback", "https://eu.ledgerly.example/oauth/callback"],
  "type": "confidential",
  "visibility": "listed",
  "review": {
    "status": "approved",
    "at": "2026-10-25T09:00:00.000Z"
  },
  "verified": true,
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"],
  "secretPrefix": "or_cs_Exampl",
  "dpopBoundAccessTokens": false,
  "status": "active",
  "suspension": null,
  "createdAt": "2026-10-25T09:00:00.000Z",
  "updatedAt": "2026-10-26T14:30:00.000Z"
}
```

## Errors

| Code                                                                                           | HTTP status | When                                              | What to do                                                                                           |
| ---------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)         | 400         | A field is invalid.                               | Fix the fields listed in `details.issues` and send the request again.                                |
| [`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.                                                           |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)       | 404         | Your organization registered no app with this ID. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)           | 409         | The app changed at the same moment; retry.        | Fetch the object again and retry the change.                                                         |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)         | 403         | You can't manage the organization's apps.         | 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_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).
