Skip to content

Approve, reject or ask for changes to a listed app

The platform.app_reviews.decide operation: POST /api/v1/platform/app_reviews/{id}/decide in the REST API and the platform_app_reviews_decide tool in the MCP server.

POST/api/v1/platform/app_reviews/{id}/decide

Records a platform admin's decision on a listed app waiting for review (spec §41.3), with a reason the publisher reads and the review checklist as ticked (stored with the decision):

  • approve: the app is listed for every organization, with a verified publisher, for every scope it asks for now. Every required checklist item must be ticked, the application complete, and the organization's acceptance of the Partner and App Terms current;
  • request_changes: the publisher edits the application and submits it again; an app approved before stays listed for its approved scopes;
  • reject: the listing ends: other organizations can no longer install the app (existing installations keep working until each organization removes them).

expectedVersion is the app's version you reviewed: a decision on an application that changed since fails with version_conflict. The owning organization gets an app.listing_* event and the app's contact an email with the reason; the reviewer isn't named. Platform admins only.

At a glance

  • REST: POST /api/v1/platform/app_reviews/{id}/decide
  • MCP tool: platform_app_reviews_decide
  • Operation ID: platform.app_reviews.decide
  • Scope: Platform
  • Permission: platform:write
  • Risk tier: write
  • Dry run: Supported (send Ownrecord-Dry-Run: true)
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: API key with the platform:write scope
  • Since: M13

Request

Path parameters

FieldTypeDescription
idocl_… ID

Body

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

FieldTypeRequiredDescription
decisionone of approve, request_changes, rejectYesapprove, request_changes or reject.
reasonstringYesWhy: what you checked, what to change or why it's rejected. The publisher reads it.
checklistobjectYesThe checklist items you ticked.
checklist.versioninteger (at least 1)YesThe review checklist's current version (platform.app_reviews.get).
checklist.itemsarray of objectYes
checklist.items[].idstringYes
checklist.items[].checkedbooleanYes
expectedVersioninteger (at least 1)YesThe app's version you reviewed.

Response

200 OK with a JSON object:

FieldTypeDescription
idocl_… ID
object"app_listing"
ownerOrgIdorg_… ID
namestring
publisherstring
visibilityone of private, listed
appStatusone of active, disabled
statusone of not_submitted, pending, changes_requested, approved, rejected
listedboolean
scopesarray of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document)
approvedScopesarray of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document) or null
pendingScopesarray of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document)
submittedAtdate-time or null
decidedAtdate-time or null
reasonstring or null
applicationobject or null
historyarray of object
history[].actionone of submitted, withdrawn, approved, rejected, changes_requested, scopes_added, identity_changed, unlisted
history[].atdate-time
history[].bystring or null
history[].reasonstring or null
history[].scopesarray of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document)
versioninteger
orgobject
org.idorg_… ID
org.slugstring
org.namestring
checklistobject or null
redirectUrisarray of string
websiteUrlstring or null
logoUrlstring or null
unjustifiedScopesarray of one of 84 values, e.g. org:read, org:write, org:admin (all in the OpenAPI document)

Examples

Approve after checking the application

HTTP
POST /api/v1/platform/app_reviews/ocl_01m4ttwd3veh9apbewxheq5xha/decide
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "decision": "approve",
  "reason": "Identity, privacy policy and questionnaire checked; scopes match the filing service.",
  "checklist": {
    "version": 1,
    "items": [
      {
        "id": "publisher_identity",
        "checked": true
      },
      {
        "id": "contact_confirmed",
        "checked": true
      },
      {
        "id": "privacy_policy",
        "checked": true
      },
      {
        "id": "questionnaire_acceptable",
        "checked": true
      },
      {
        "id": "scopes_justified",
        "checked": true
      },
      {
        "id": "redirect_uris",
        "checked": true
      },
      {
        "id": "branding",
        "checked": true
      }
    ]
  },
  "expectedVersion": 3
}

Response (200):

JSON
{
  "id": "ocl_01m4ttwd3veh9apbewxheq5xha",
  "object": "app_listing",
  "ownerOrgId": "org_01m4ttwd3veh9apbewxheq5xhb",
  "name": "Ledgerly AI",
  "publisher": "Ledgerly AI B.V.",
  "visibility": "listed",
  "appStatus": "active",
  "status": "approved",
  "listed": true,
  "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"],
  "approvedScopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"],
  "pendingScopes": [],
  "submittedAt": "2026-10-30T09:00:00.000Z",
  "decidedAt": "2026-10-31T10:15:00.000Z",
  "reason": "Identity, privacy policy and questionnaire checked; scopes match the filing service.",
  "application": {
    "publisher": {
      "legalName": "Ledgerly AI B.V.",
      "country": "NL",
      "websiteUrl": "https://ledgerly.example",
      "contact": {
        "name": "Mira de Vries",
        "email": "partners@ledgerly.example"
      }
    },
    "privacyPolicyUrl": "https://ledgerly.example/privacy",
    "questionnaire": {
      "version": 1,
      "answers": [
        {
          "questionId": "data_location",
          "answer": null,
          "details": "AWS eu-central-1 (Frankfurt), Germany"
        },
        {
          "questionId": "encryption_in_transit",
          "answer": "yes",
          "details": null
        },
        {
          "questionId": "encryption_at_rest",
          "answer": "yes",
          "details": null
        },
        {
          "questionId": "credential_storage",
          "answer": "yes",
          "details": null
        },
        {
          "questionId": "staff_access",
          "answer": "yes",
          "details": null
        },
        {
          "questionId": "incident_response",
          "answer": "yes",
          "details": null
        },
        {
          "questionId": "subprocessors",
          "answer": "yes",
          "details": "AWS (hosting), Postmark (email)"
        },
        {
          "questionId": "model_training",
          "answer": "no",
          "details": null
        },
        {
          "questionId": "data_deletion",
          "answer": "yes",
          "details": "Within 30 days of uninstalling"
        },
        {
          "questionId": "vulnerability_management",
          "answer": "yes",
          "details": null
        }
      ]
    },
    "scopeJustifications": [
      {
        "scope": "filings:read",
        "justification": "Reads filing periods to prepare each return."
      },
      {
        "scope": "filings:write",
        "justification": "Records the prepared return and its submission."
      },
      {
        "scope": "tasks:read",
        "justification": "Finds the filing tasks assigned to the app."
      },
      {
        "scope": "tasks:write",
        "justification": "Claims and completes filing tasks."
      },
      {
        "scope": "ledger:read",
        "justification": "Reconciles the return with the tax payable accounts."
      }
    ],
    "partnerTermsVersion": "1.0",
    "submittedBy": "usr_01m4ttwd3veh9apbewxheq5xhf"
  },
  "history": [
    {
      "action": "submitted",
      "at": "2026-10-30T09:00:00.000Z",
      "by": "usr_01m4ttwd3veh9apbewxheq5xhf",
      "reason": null,
      "scopes": []
    },
    {
      "action": "approved",
      "at": "2026-10-31T10:15:00.000Z",
      "by": "usr_01m4ttwd3veh9apbewxheq5xhg",
      "reason": "Identity, privacy policy and questionnaire checked; scopes match the filing service.",
      "scopes": ["filings:read", "filings:write", "tasks:read", "tasks:write", "ledger:read"]
    }
  ],
  "version": 4,
  "org": {
    "id": "org_01m4ttwd3veh9apbewxheq5xhb",
    "slug": "ledgerly",
    "name": "Ledgerly AI"
  },
  "checklist": {
    "version": 1,
    "items": [
      {
        "id": "publisher_identity",
        "checked": true
      },
      {
        "id": "contact_confirmed",
        "checked": true
      },
      {
        "id": "privacy_policy",
        "checked": true
      },
      {
        "id": "questionnaire_acceptable",
        "checked": true
      },
      {
        "id": "scopes_justified",
        "checked": true
      },
      {
        "id": "redirect_uris",
        "checked": true
      },
      {
        "id": "branding",
        "checked": true
      }
    ]
  },
  "redirectUris": ["https://ledgerly.example/oauth/callback"],
  "websiteUrl": "https://ledgerly.example",
  "logoUrl": null,
  "unjustifiedScopes": []
}

Errors

CodeHTTP statusWhenWhat to do
app_review_not_pending409The app isn't waiting for review.Reload the review queue: the application was already decided or withdrawn, or the partner hasn't submitted its changes yet.
app_review_checklist_incomplete400An approval without every required checklist item ticked.Check each required item of the review checklist and tick it, or request changes or reject with a reason instead. details.missing lists the items.
app_listing_incomplete400An approval of an application that is missing or incomplete (e.g. an added scope without a justification).Answer every required question of the current security questionnaire, justify every scope the app asks for, and give a privacy policy URL. details.problems lists what's missing.
partner_terms_not_accepted409An approval while the organization's Partner and App Terms acceptance isn't current.An owner or admin reads the Partner and App Terms at /legal/partners and accepts them under Developers → Apps (or with partner_terms.accept), then submits the application again.
validation_failed400No reason, a checklist version that isn't the current one, or an unknown checklist item.Fix the fields listed in details.issues and send the request again.
version_conflict409The app changed since you reviewed it (expectedVersion); reload it.Fetch the object again and retry the change.
resource_not_found404No registered app has this ID.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
permission_denied403You aren't a platform admin.Ask an organization owner or admin for a role or scope that allows this action.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, rate_limited, internal_error.