---
title: Common scenarios
description: The situations partners hit most often with delegated access, what happens in each, and what to build.
---

Most questions about delegated access are one of these. Each says what you
are trying to do, what happens, and what to build.

## You want to test before going live

**What happens.** Your live app needs ZevID's approval and a verified domain.
Your test environment needs neither, but only your own team can sign in to it
or connect it, and test and live never mix.

**Build this.** Test with your test environment and your team's own ZevID
accounts. Add teammates under **Team** in the console. Test connections end
after a set number of days, shown in the console; reconnect to keep testing. See
[Test environments](/delegated-access/testing/).

## Your app already uses ZevAuth for sign-in, then asks for ZevCloud access

**What happens.** The user already has a ZevID account and a ZevID session
from signing in to you, so they see the consent screen only, with no second
sign-in. High-risk permissions may still ask them to "Confirm it's you".

**Build this.** Tell ZevID who you expect: build the URL on your server with
your ZevAuth `user_id`, or send their ZevID account id as `login_hint`. Only that account can
approve. Check that `account_id` in the token response is the same user. We
check it too, and revoke a connection that came back for anyone else. See
[The flow](/delegated-access/flow/#tell-zevid-who-you-expect).

## Your app has its own sign-in, not ZevAuth

**What happens.** The user signs in to ZevID once, on the consent screen, the
way they would for "Connect GitHub". That proves they own the product
account. It does not replace your sign-in.

**Build this.** Store the connection (`grant_id`) against **your own user**,
in the session that started the flow. Never match users by email.

## The browser is signed in to a different ZevID account than your user

**What happens.** With a hint (`login_hint`, or `user_id` on the server call), ZevID asks the user to
switch to the right account. If they cannot or will not, you get
`error=access_denied` with `error_reason=account_mismatch`.

**Build this.** Tell the user to continue with the ZevID account they use with
you. Never link a connection to a different account.

## The user has never used the product

**What happens.** ZevCloud creates their account the first time they arrive
with their ZevID. The consent screen shows ZevCloud's own "Create a team"
button, which brings them straight back to finish connecting.

**Build this.** Nothing extra. **Never create product accounts or teams for
users**; only the person can, in the product.

## None of the user's teams can be picked

**What happens.** Teams the user cannot connect, for example where they are
only a member, are shown greyed out with the product's reason, such as "Only
owners and admins can connect apps".

**Build this.** Explain the requirement in your UI before you send them to
connect.

## The user allows the connection but leaves a high-risk permission unticked

**What happens.** The connection works without it.

**Build this.** Always read the granted `scope`. Offer the feature again later
through authorize. See [High-risk permissions](/delegated-access/high-risk/).

## A high-risk permission ended after its confirmation window

**What happens.** Tokens stop carrying it; the rest of the connection keeps
working. Product calls that need it answer `403 high_risk_confirmation_required`.
A refresh answers `invalid_grant` with `reason: high_risk_expired` only when
nothing else usable is left on the connection.

**Build this.** Send the user through authorize with `prompt=reconfirm`. Do not
ask them to reconnect from scratch.

## The user wants to add or remove a team later

**What happens.** They can remove one from **Apps and websites** in ZevID.
Adding one always starts from your app.

**Build this.** Offer a **Manage teams** button that starts authorize with
`prompt=configure`.

## You need two Zev products

**What happens.** Each product is a separate approval, consent and connection.

**Build this.** Run one authorize per product and store each `grant_id`
separately.

## Access stops working

**What happens.** The user disconnected, removed a team or lost their role;
the product or ZevID paused a permission or suspended your app; a test
connection ended; or a refresh token was reused. Each arrives as a
[webhook](/delegated-access/webhooks/) with a `reason`, and as an error with
the same reason.

**Build this.** Handle events and errors by `reason`:

- reconnect for `grant_revoked`, `refresh_reused` and `test_grant_expired`;
- reconfirm for `high_risk_expired`;
- wait for ZevID on pauses and suspensions (`scope_suspended`,
  `grant_suspended`, `app_suspended`).

The full list is on [Errors](/delegated-access/errors/).

## Two servers refresh the same connection at once

**What happens.** It looks like theft: every refresh token of that connection
is revoked and the user must reconnect.

**Build this.** Serialise refreshes per connection, for example with a lock on
`grant_id`, and always store the newest refresh token. See
[Refresh tokens](/delegated-access/refresh-tokens/).

> **Note:**
> Not here? The [Errors](/delegated-access/errors/) page lists every reason and
> what to do with it.
