# auth.md

You are an agent. This document is visti's **agentic registration** recipe: how
you get authorized to send notifications to a human's inbox, and how that
authorization is provisioned, used, and revoked.

visti runs a standard **OAuth 2.1 authorization server**. If your client already
speaks the MCP authorization flow, you need nothing from this page — connect to
`https://visti.sh/mcp`, follow the 401, and you are done. The rest is detail.

## The short version

If your client can add an MCP server, add `https://visti.sh/mcp` and it will do
all of this for you. The `visti` skill
(`npx skills add https://visti.sh/`) covers connecting and what to send once you
are connected; this page is only about authorization.

## Discover

```http
GET /.well-known/oauth-protected-resource
GET /.well-known/oauth-authorization-server
```

The first (RFC 9728) names the protected resource — `https://visti.sh/mcp` —
its authorization server, and the scopes. The second (RFC 8414) names the
endpoints: `/authorize`, `/token`, `/register`, `/revoke`, PKCE `S256` only,
public clients only.

You will also reach the first document without asking. An unauthorized
`tools/call` answers `401` with:

```http
WWW-Authenticate: Bearer realm="visti", error="invalid_request",
  error_description="...", resource_metadata="https://visti.sh/.well-known/oauth-protected-resource"
```

That header is the entry point. Follow it.

## Identify yourself

Two ways, both accepted:

- **Dynamic registration** (RFC 7591) — `POST /register` with a `client_name`
  and your `redirect_uris`. You get back a `client_id`, plus a
  `registration_access_token` and `registration_client_uri` you can later use to
  read or delete your own registration (RFC 7592). No client secret is issued:
  you are a public client and PKCE is what protects you.
- **Client ID Metadata Document** — if you have a stable public HTTPS URL
  serving your own OAuth metadata, use that URL as your `client_id` and skip
  registration entirely. The document's own `client_id` must equal the URL it
  is served from.

## Get a token

Ordinary authorization code flow with PKCE:

1. `GET /authorize` with `response_type=code`, your `client_id`,
   a registered `redirect_uri`, `code_challenge` + `code_challenge_method=S256`,
   `state`, and an optional `scope`. **PKCE is mandatory; there is no plain
   method and no path that skips it.**
2. visti sends the human to `id.everruns.com` to sign in, then shows them a
   consent screen naming your client, what you asked for, and where the token
   will be delivered. visti never sees their password.
3. Your `redirect_uri` receives `code`; exchange it at `POST /token` with your
   `code_verifier`. Codes are single-use.
4. You get an access token (30 days) and a refresh token (180 days). **Refresh
   tokens rotate** — each `refresh_token` grant retires the one you presented.

Scopes: `visti.send` (post notifications), `visti.feedback` (responses to this client's own updates), `visti.read` (read the inbox),
`visti.manage` (channels, keys, vault). Ask for nothing and you get
`visti.send`, which is what an agent needs.

## Use the credential

```http
POST /mcp
Authorization: Bearer visti_at_...
```

Then call `visti_send`. You do **not** pass a channel: omit `channel` and your
notification lands in a channel named after your client, created on first send
and owned by the person who authorized you. Name a `channel` (lowercase letters,
digits, hyphens) to group a workstream — `visti_channels` lists what exists.

## Revoke

`POST /revoke` with your token disconnects you: every access and refresh token
on that grant is deleted, not just the one you sent. `DELETE` your
`registration_client_uri` to remove the registration itself.

The human can revoke you at any time from their side. Expect a `401`, and treat
it as a signal to re-authorize rather than to retry.

## Moving existing clients to OAuth

Non-OAuth MCP access is retired. Existing `vsk_…` access keys in headers and
legacy `channel_key` arguments are rejected on every MCP method, including
`initialize` and `tools/list`. Reconnect with OAuth; moving a key into a header
or rotating it does not restore sending.

1. Sign in from your existing Visti Mac app (Settings) or browser inbox first.
   This attaches the existing inbox and channels to your account.
2. Reconnect `https://visti.sh/mcp` in your agent's MCP client, sign in with the
   same account, and approve sending. Remove old key headers and arguments.
3. Omit `channel` for the connection's new default, or use `visti_channels` to
   find an existing owned channel and pass its name to keep sending there.

For CI and scripts, authorize once with an OAuth-capable client, then securely
store the OAuth access and refresh tokens outside model context. Refresh before
expiry and persist each rotated refresh token. There is no permanent access-key
fallback. If your client cannot do OAuth, use an OAuth-capable client or tell
your human that the integration needs updating; do not retry forever.

Every unauthorized MCP request, including `initialize` and
`tools/list`, receives `401` with OAuth discovery metadata and reconnect
instructions. Notifications, cards and retries all require OAuth.
Existing messages, channels, pairing and read access are retained.
