# Revoke a Grant

A **grant** records that a User authorized an Application to reach a specific Resource on their behalf. When a User approves an Application on a [consent screen](/concepts/applications/#consent), Keycard stores a grant that captures the Resource, the [Provider](/concepts/providers/) behind it, and the scopes approved. From then on, Keycard [issues credentials](/concepts/credentials/) for that access without prompting the User again.

Revoking a grant withdraws that authorization. You'd do this when:

- **Offboarding** a person or decommissioning an Application.
- A **compromised or misbehaving agent** needs to be cut off.
- An Application ended up **over-scoped** and you want to force it back through consent.
- You're **wrapping up a proof of concept** and want to clean up the access it accumulated.

> **Note: Grant vs. policy vs. credential** These three controls are related but distinct:
- A [**policy**](/admin/access-policies/) decides whether a User and Application are *allowed* to access a Resource. It's a standing rule you author.
- A **grant** records that a User *authorized* a specific Application to act for them against a Resource. It's the consent, captured per User.
- A [**credential**](/concepts/credentials/) is the short-lived token Keycard issues once policy and consent both pass.
Revoking a grant removes the authorization. It doesn't change your policies, and — as covered [below](#what-happens-after-you-revoke) — it doesn't reach back and kill credentials that were already issued.

## Who can revoke grants

Grants live in your organization, and revoking one is a management operation, so it requires a management role:

- **Admins** (organization-wide) and **Managers** of a [custom Zone](/concepts/zones/) can view and revoke grants for people in that scope.

**Viewers** are read-only: they can see grants but not revoke them.

- [Roles & Permissions](/admin/roles-and-permissions/)

## Before you begin

- Sign in to the [Keycard Console](https://console.keycard.ai) with an Admin or Manager role.
- Know which **person** holds the grant. Grants are managed per User from the **People** page.
- People and grants live in your **organization** by default. If the access was granted in a [custom Zone](/concepts/zones/), switch to that Zone from the **Zones** page first.
- For the management API, have your organization's `<zone-id>` (its org Zone) and a service-account token.

## Revoke a grant

**Console:**

1. **Open the person's profile**

   In the left nav, go to **People** and click the person whose access you want to withdraw. Their profile shows their identifiers, last activity, and two tabs: **Sessions** and **Grants**.

2. **Open the Grants tab**

   Select **Grants**. Each row is one grant, showing the **Entity** (the Resource, such as `Google Calendar API`), its **Provider**, the approved **Scopes**, a **Status** (`Active now` or `Revoked`), and when it **Expires** (for example, `refreshable`). Active grants are listed first.

3. **Revoke the grant**

   On the grant's row, open the actions menu (**⋯**) and choose **Revoke grant**.

4. **Confirm the status flipped**

   The grant's **Status** changes to **Revoked**. Use **Refresh** if the list doesn't update immediately. See [Verify the revocation](#verify-the-revocation) to confirm the effect on live traffic.

**HTTP:**

All management API requests use a Bearer token obtained from your service account's client credentials. In the paths below, `<zone-id>` is your organization's Zone (its org Zone), or a custom Zone's ID if the grant lives there.

1. **Authenticate**

   Exchange the service account's client ID and client secret (exported as `KEYCARD_CLIENT_ID` and `KEYCARD_CLIENT_SECRET`) for a token:

   ```bash frame="none"
   ACCESS_TOKEN=$(curl -s -X POST https://api.keycard.ai/service-account-token \
     -d "grant_type=client_credentials" \
     -d "client_id=$KEYCARD_CLIENT_ID" \
     -d "client_secret=$KEYCARD_CLIENT_SECRET" \
     | jq -r '.access_token')
   ```

2. **List the grants in the Zone**

   Find the grant you want to revoke. Filter by the person's user ID, and optionally `status=active`.

   ```bash frame="none"
   curl "https://api.keycard.ai/zones/<zone-id>/delegated-grants?user_id=<user-id>&status=active" \
     -H "Authorization: Bearer $ACCESS_TOKEN"
   ```

   Each entry in `items` has an `id` — that's the grant you'll revoke.

3. **Revoke the grant**

   Set the grant's status to `revoked`. This mirrors the Console's **Revoke grant** action: the grant stays listed with **Status: Revoked**.

   ```bash frame="none"
   curl -X PATCH https://api.keycard.ai/zones/<zone-id>/delegated-grants/<grant-id> \
     -H "Authorization: Bearer $ACCESS_TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"status": "revoked"}'
   ```

   To permanently remove the grant instead of marking it revoked, send `DELETE` to the same path — it returns `204 No Content`.

**Python:**

All management API requests use a Bearer token obtained from your service account's client credentials. Here `zone_id` is your organization's Zone (its org Zone), or a custom Zone's ID if the grant lives there.

1. **Authenticate**

   Exchange the service account's client ID and client secret for a token:

   ```python
   import requests

   token_response = requests.post(
       "https://api.keycard.ai/service-account-token",
       data={
           "grant_type": "client_credentials",
           "client_id": KEYCARD_CLIENT_ID,
           "client_secret": KEYCARD_CLIENT_SECRET,
       },
   ).json()

   base = "https://api.keycard.ai"
   headers = {"Authorization": f"Bearer {token_response['access_token']}"}
   ```

2. **List the grants in the Zone**

   ```python
   grants = requests.get(
       f"{base}/zones/{zone_id}/delegated-grants",
       headers=headers,
       params={"user_id": user_id, "status": "active"},
   ).json()

   for grant in grants["items"]:
       print(grant["id"])
   ```

3. **Revoke the grant**

   Set the grant's status to `revoked` — the same action as the Console's **Revoke grant**:

   ```python
   requests.patch(
       f"{base}/zones/{zone_id}/delegated-grants/{grant_id}",
       headers=headers,
       json={"status": "revoked"},
   )
   ```

   To permanently remove the grant instead, use `requests.delete(...)` on the same path.

> **Note: No dedicated CLI command** The Keycard [CLI](/cli/) doesn't have a grant-revocation command today. If you script against the management API, you can reach the same endpoint through the generic passthrough, which authenticates against the Management API:
```bash frame="none"
keycard agent api /zones/<zone-id>/delegated-grants/<grant-id> \
-X PATCH -d '{"status": "revoked"}'
```

## What happens after you revoke

Revoking a grant stops Keycard from issuing **new** access for that User and Resource. It does **not** reach out and invalidate credentials that are already in the wild.

**New credential requests stop.** The next time the Application asks Keycard for a credential for that access — a fresh [token exchange](/concepts/credentials/#delegation-chaining) or a [refresh](/concepts/credentials/#refreshing) — Keycard has no grant to satisfy it and the request fails with `insufficient_authorization` (an `OAuth2Error`) and the message *User authorization is required for resource `<resource-url>`*. The User has to authorize the access again before anything is issued, so the Application should treat this as a re-authentication prompt.

**Already-issued credentials live until they expire.** Keycard issues short-lived credentials, but there is no per-token kill-switch today. A token the agent already holds keeps working against the Resource until it expires on its own. In practice, access stops within the lifetime of the current credential — not the instant you click **Revoke grant**.

**Refresh stops.** A grant that shows `refreshable` in the Console can mint new credentials without re-prompting the User. Revoking the grant ends that — refresh attempts fail, so the Application can't quietly extend its access past the current token.

**Brokered credentials are a special case.** For [brokered access](/concepts/resources/#brokered-access) to an external Provider (Google, GitHub, and so on), revoking the Keycard grant stops Keycard from brokering *new* credentials — but it does not sign the User out at the Provider or revoke a token the Provider already issued. To fully cut off external access, also revoke Keycard's access from the Provider's own connected-apps settings.

**What the agent or Application sees.** The current token keeps working against the Resource until it expires; after that, the Application can no longer obtain a new credential. Its next token request to Keycard fails with `insufficient_authorization` — the User must re-authorize. That's distinct from `access_denied`, which is what a [policy](/admin/access-policies/) denial returns.

> **Caution: One grant at a time** Revoking a grant is per-Resource, and there's no per-token kill-switch — you can't revoke a single outstanding credential. To cut a person off from *everything* at once rather than grant by grant, [disable or remove the person](#cut-a-person-off-entirely) instead.

## Cut a person off entirely

When you're offboarding someone or responding to a compromise, revoking grants one at a time is slower than you want. From the **People** page, open the person's access drawer (the **⋯** on their row) and use the **Danger zone**:

- **Disable user** blocks the person from signing in or obtaining new credentials while keeping their account and configuration intact. It's reversible.
- **Remove from organization** removes the person entirely.

You can't disable or remove yourself.

Disabling or removing a person stops new credential issuance the same way revoking a grant does; credentials their Applications already hold keep working until they expire.

## Verify the revocation

1. **Check the grant status.** On the person's **Grants** tab, the revoked grant now shows **Status: Revoked**.

2. **Check the Audit Log.** From the grant's actions menu, choose **View in audit log**, or open the [Audit Log](/admin/audit-log-export/) directly. After revocation, the Application's next attempt shows as a **Failure** on the credential exchange (resource path `/oauth/2/token`), with error code `insufficient_authorization` and the message *User authorization is required for resource `<resource-url>`*. The event's actor is the full identity chain — the Application and the User it acted for.

3. **Retry from the Application.** Once the current credential expires, trigger the Application again. Its next token request to Keycard should fail with `insufficient_authorization`, and the **Grants** tab should still show the grant as **Revoked**.

## Re-grant access

Revocation isn't permanent. To restore access, the User goes back through the [authorization flow](/concepts/credentials/#user-delegation) and approves the [consent screen](/concepts/applications/#consent) again. Keycard records a new grant and credential issuance resumes.

For an Application configured with implicit consent, there's no screen to approve — the grant is re-created automatically the next time the User runs the flow.

## Related

- [Access Policies](/admin/access-policies/) — the standing rules that decide who's allowed access in the first place
- [Credential Issuance](/concepts/credentials/) — how Keycard issues, refreshes, and brokers the credentials a grant unlocks
- [Applications](/concepts/applications/#consent) — the consent model and how grants are created
- [Providers](/concepts/providers/) — the external providers behind brokered grants
- [Audit Log Export](/admin/audit-log-export/) — stream credential and access events to your SIEM
- [Roles & Permissions](/admin/roles-and-permissions/) — who can perform management operations
