# CLI device login

> Sign the vy CLI in through the browser with the device authorization grant, or give it a key on stdin.

`vy login` signs the CLI in without copying secrets around. It uses the OAuth 2.0 device authorization grant (RFC 8628): the CLI shows a code, you approve it in the console, and the CLI receives an ordinary [API key](/docs/guides/api-keys).

## Sign in

```sh
export VMYARD_API_URL=https://api.example.com
vy login
```

1. The CLI prints a URL and a code such as `BCDF-GHJK`, and opens the console's **/device** page (pass `--no-browser` to open it yourself, for example over SSH).
2. In the console, check that the code and the device name (`vy <version> on <hostname>`) match what your terminal shows. **Approve only a request you started.**
3. Choose the organization, the key's preset, its lifetime (1–90 days, 90 by default), and its name, then approve.
4. The CLI receives the key and stores it with its organization.

`--scope` asks for a preset: `read_only`, `use_existing`, `create_and_use` (the default), or `full_access` (needed for webhooks). The person approving can choose a different one. Any member of the organization can approve; the key is limited by their role like any key.

```sh
vy login --scope full_access
vy whoami
```

## Other ways to sign in

```sh
vy login --key-stdin < ~/vmyard-key.txt   # a key you created in the console
export VMYARD_API_KEY=vmy_...             # or set it in the environment
```

`VMYARD_API_KEY` always wins over a stored key. `--key-stdin` checks the key with `/v1/me` before storing it (`--no-verify` skips the check). The CLI never accepts a key as a command-line argument and never prints a key.

## Where the key is stored

| System | Path |
| --- | --- |
| Linux and others | `$XDG_CONFIG_HOME/vmyard/config.json` (default `~/.config/vmyard/`) |
| macOS | `~/Library/Application Support/vmyard/config.json` |
| Windows | `%APPDATA%\vmyard\config.json` |

`VMYARD_CONFIG_DIR` overrides the directory. The file is written with mode `0600` in a `0700` directory, and `vy` warns if other users can read it.

`vy logout` deletes the stored key from this computer only. The key itself stays valid until it expires; revoke it in the console under **API keys** if you no longer need it. Each `vy login` creates a new key.

## The protocol

Other tools can use the same flow; the SDKs ship it as `DeviceAuthorization`.

1. `POST /v1/auth/device/code` with optional `client_name` and `scope`. No authentication. The answer has `device_code` (keep it secret), `user_code`, `verification_uri`, `verification_uri_complete`, `expires_in` (600 seconds), and `interval` (5 seconds).
2. Show the user `user_code` and `verification_uri`.
3. Poll `POST /v1/auth/device/token` with `grant_type` `urn:ietf:params:oauth:grant-type:device_code` and the `device_code`, waiting `interval` seconds between polls.

```sh
curl -X POST "$VMYARD_API_URL/v1/auth/device/token" \
  -H "Content-Type: application/json" \
  -d '{"grant_type": "urn:ietf:params:oauth:grant-type:device_code", "device_code": "'"$DEVICE_CODE"'"}'
```

Until the user decides, polls answer `400` with an RFC 8628 error:

| `error` | What to do |
| --- | --- |
| `authorization_pending` | Keep polling. |
| `slow_down` | You polled too fast. Add 5 seconds to the interval. |
| `access_denied` | The user denied it, or the approver left the organization. Stop. |
| `expired_token` | The code expired. Start again. |
| `invalid_grant` | Unknown or already used device code. Start again. |
| `invalid_request` | The request is malformed. |
| `unsupported_grant_type` | Wrong `grant_type`. |

After approval, the next poll returns the key once: `access_token` (the `vmy_…` secret), `token_type: "Bearer"`, `expires_in` (seconds until the key expires), `scope` (the preset), and `api_key` (its details).

Requesting codes is limited to 30 per network address per 10 minutes.
