---
title: "Authorize an App"
method: POST
path: "/users/me/oauth_grants"
tags: ["Users"]
---

# Authorize an App

`POST /users/me/oauth_grants`

Completes the OAuth authorization step for the authenticated user: records their consent for the scopes an app asked for and mints the authorization code to hand back to it. Returns the grant, plus a `redirect_url` carrying that code — the one and only time it is returned. Exchange the code at `POST /oauth/token` with the verifier for `code_challenge`. Requires a user session, because consent has to come from the account holder: an API key or an OAuth token is refused, so an app can never authorize itself. Send an `Idempotency-Key` to make a retry safe — a replay returns the original `redirect_url` and its code rather than issuing a second one.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `account_id` string — Authorize the app for one of the user's accounts rather than for the user alone, prefixed `biz_`. The user must have access to it.
  - `client_id` string, required — The app being authorized, prefixed `app_`.
  - `code_challenge` string, required — The PKCE code challenge: the base64url-encoded SHA-256 of your code verifier, without padding.
  - `code_challenge_method` 'S256', required — How `code_challenge` was derived. Only `S256` is accepted.
  - `consent_shown` boolean — Whether the consent UI listed these scopes for the user. Sending `false` succeeds only when the user has already granted every scope requested.
  - `nonce` string — OIDC nonce, echoed into the resulting ID token. Required when `requested_scopes` includes `openid`.
  - `redirect_uri` string, required — Where to send the user once they have consented. Must match one of the app's registered redirect URIs exactly — it is compared as a string, not normalized.
  - `requested_scopes` string[], required — The permissions the app is asking for, for example `member:basic:read`. `GET /api_keys/permissions` names and describes each one. Granting adds to whatever the user already granted this app rather than replacing it.
  - `response_type` 'code' — The OAuth response type. Only `code` is accepted; defaults to `code`.
  - `state` string — Opaque value appended to `redirect_url` unchanged, for the client to correlate the response with its request.

## Response `201`

the app is authorized

- OauthGrant
  - `account_id` string, nullable, required — The account the grant is scoped to, prefixed `biz_`. `null` when the user authorized the app for themselves rather than for one of their accounts.
  - `app_id` string, required — The app this grant authorizes, prefixed `app_`.
  - `authorized_at` string, nullable, required — When the user last authorized the app, as an ISO 8601 timestamp.
  - `created_at` string, required — When the user first authorized the app, as an ISO 8601 timestamp.
  - `id` string, required — Grant ID, prefixed `oag_`.
  - `redirect_url` string — Where to send the user to finish the flow: the `redirect_uri` you supplied with the authorization `code` appended, and `state` when you supplied one. Its scheme, host, port, and path come back exactly as sent — never re-cased or re-encoded — because the client matches them against its registered URI. Returned only once, on create: the code is single-use and expires 10 minutes after it is issued, so redirect immediately.
  - `revoked_at` string, nullable, required — When the grant was revoked, as an ISO 8601 timestamp, or `null` while it is still in force. A revoked grant authorizes nothing — treat its `scopes` as no longer granted.
  - `scopes` string[], required

## Other responses

- `400` — Invalid Parameters
- `401` — Unauthorized
- `403` — Forbidden
- `404` — Resource not found

---

[API](https://skmtc.net/whop/apis/whop-api.md) · [All operations](https://skmtc.net/whop/apis/whop-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/whop/whop-api/versions/db0883548bc5/schema)
