---
title: "Approve or deny a pending client inline (approval-pending page)"
method: POST
path: "/oauth/approval/decision"
tags: ["OAuth"]
---

# Approve or deny a pending client inline (approval-pending page)

`POST /oauth/approval/decision`

Thin wrapper over the admin approval path for the approval-pending page.

Authorization is byte-identical to ``POST /admin/oauth-clients/{id}:approve``
/ ``:deny`` (``oauth-clients:write``, org:admin implies it) and the
decision itself is the SAME ``OAuthClientService.approve``/``deny`` calls —
same audit records, same D7 active/approval_status coupling; this endpoint
only translates the signed state blob into the client row. CSRF posture
matches the consent POST: no ambient credential is honored — the browser
must explicitly present the SPA bearer token, which a cross-site form
cannot do.

## Request body

- OAuthApprovalDecisionRequest — Inline admin approve/deny posted from the approval-pending page. ``state`` is the signed approval-state blob minted by ``/authorize`` for this exact authorize request — the decision endpoint never accepts a bare ``client_id``. ``state`` is deliberately NOT marked x-sensitive: the CLI's GEN-21 redaction backstop unions every sensitive field's BARE name globally, and "state" is generic enough to redact unrelated CLI output (e.g. the MCP session-diagnosis ``state`` field). The blob is not a lasting bearer credential — it is HMAC-signed, purpose-discriminated, TTL'd (600 s), and the decision endpoint additionally requires an authenticated admin with ``oauth-clients:write`` — so global redaction buys nothing worth that collision.
  - `action` 'approve' | 'deny', required
  - `state` string, required

## Response `200`

Successful Response

- OAuthApprovalStatusResponse — Minimal tri-state approval status for a pending-client authorize request. Deliberately carries nothing else — no client name, redirect URIs, or metadata — so the anonymous poll endpoint cannot be used to read client details out of the registry.
  - `status` 'pending' | 'approved' | 'denied', required

## Other responses

- `400` — Malformed, tampered, or expired approval-state blob.
- `401` — Unauthorized
- `403` — Forbidden
- `422` — Unprocessable Entity
- `500` — Internal Server Error
- `503` — Service Unavailable

## Changes

- **2026-09-07** `97a326fd99b4` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/jentic/apis/jentic-control-plane-api/changes/oauth/approval/decision/post.md)

---

[API](https://skmtc.dev/jentic/apis/jentic-control-plane-api.md) · [All operations](https://skmtc.dev/jentic/apis/jentic-control-plane-api/llms.txt) · [OpenAPI document](https://skmtc.dev/jentic/apis/jentic-control-plane-api/revisions/e4688b93dfc7?raw)
