---
title: Revocation
description: Every way a connection ends, how your app notices, and how to disconnect a user from your side.
---

The user is always in control of a connection. It can end, or narrow, in four
ways:

| Who | What happens |
| --- | --- |
| **The user** | Disconnects your app in their ZevID account under **Apps and websites**, or removes one target. Removing the last target disconnects. |
| **You** | Call the revoke endpoint below, for example when the user clicks Disconnect inside your app. |
| **The product** | Removes a target when the user loses their role on it, or the target is deleted. It can also suspend your app on that product. |
| **ZevID** | Suspends one connection, your app on one product, or your app on every product. |

A revoke takes effect immediately. A suspension blocks refresh and new
authorizations until it is lifted.

## Revoke a connection

```
POST /v1/partner/delegated/revoke
```

Server side only, with your **secret** key (`sk_live_…`, or `sk_test_…` for a test connection). Send the `grant_id` from the
token response.

```bash
curl https://api.zevauth.net/v1/partner/delegated/revoke \
  -H "Authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{ "grant_id": "6d1d2c3b-4a5f-4e6d-8c7b-9a0b1c2d3e4f", "reason": "user disconnected in app" }'
```

`reason` is optional, up to 200 characters, and for your own records.

```json
{
  "grant_id": "6d1d2c3b-4a5f-4e6d-8c7b-9a0b1c2d3e4f",
  "status": "revoked",
  "gv": 7,
  "revoked_at": "2026-10-20T09:12:44.000Z"
}
```

It is **idempotent**: revoking an already revoked connection succeeds again.
A `grant_id` that is not one of your connections returns
`404 grant_not_found`. The user does not get an email when you revoke.

## How you notice

| Signal | Means |
| --- | --- |
| `delegated_access.connection.revoked` webhook | The connection is gone. `by` says who. |
| `delegated_access.connection.updated` with `targets_removed` or `suspended` | It narrowed, or is paused for now. |
| `400 invalid_grant` on refresh | The connection is revoked or suspended, or the refresh token is no longer valid. |
| `401` from the product's API | The access token is no longer accepted. |

Access tokens already issued last at most 10 minutes, and products check every
call against the current state of the connection, so a revoke is honoured
within seconds.

## What to do

1. Delete the stored refresh token for that connection. It will never work
   again.
2. Mark the connection disconnected in your app.
3. Show the user a **Reconnect** button that starts the
   [flow](/delegated-access/flow) again. For a removed target, a
   `prompt=configure` link lets them pick targets again.

> **Note:**
> Do not retry a refresh that failed with `invalid_grant`. Reconnecting is the
> only way back, and it needs the user.
