> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usehasp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Device Authorization

> RFC 8628 device authorization grant — the headless/SSH fallback used when no browser or loopback listener is available.

Most `hasp login` flows use a browser redirect with a local loopback listener (see [OAuth Quickstart](/platform/oauth/quickstart)). That doesn't work over SSH, in a container, or on a machine with no browser. The **device authorization grant** (RFC 8628) is the fallback: the CLI displays a short code, you enter it on any other device with a browser, and the CLI polls for completion.

This is a `hasp` CLI concern, not something most API integrators need to implement directly — it's documented here because the two endpoints it uses are part of the public `/v1` OAuth surface.

## Flow overview

1. The CLI (auto-detecting no browser/loopback available) requests a device code.
2. It shows you a short `user_code` and a URL to visit.
3. You open the URL on any device, sign in, and approve.
4. Meanwhile the CLI polls the token endpoint until you approve (or the code expires).

## 1. Request a device code

```
POST /v1/oauth/device_authorization
```

Stateless, unauthenticated (the device\_code itself becomes the proof of possession). Rate-limited (60 requests/minute) since it's public and DB-writing.

```bash theme={null}
curl -X POST https://api.usehasp.com/v1/oauth/device_authorization \
  -d "client_id=hasp-cli" \
  -d "scope=hasp.data.read hasp.data.write"
```

```json theme={null}
{
  "device_code": "e6cf...redacted",
  "user_code": "WXYZ-ABCD",
  "verification_uri": "https://app.usehasp.com/v1/oauth/device",
  "verification_uri_complete": "https://app.usehasp.com/v1/oauth/device?user_code=WXYZ-ABCD",
  "expires_in": 900,
  "interval": 5
}
```

| Field                       | Meaning                                                                                       |
| --------------------------- | --------------------------------------------------------------------------------------------- |
| `device_code`               | Opaque, long-lived poll token. Never displayed to the user — only used by the polling client. |
| `user_code`                 | Short, human-enterable code shown to the user.                                                |
| `verification_uri`          | Where the user enters `user_code` manually.                                                   |
| `verification_uri_complete` | Same page, pre-filled — use this for a QR code or clickable link.                             |
| `expires_in`                | Seconds until the device code expires (900s / 15 minutes).                                    |
| `interval`                  | Minimum seconds to wait between polls (see Polling below).                                    |

An unknown or inactive `client_id` returns a `400` with the standard RFC 6749 shape: `{"error": "invalid_client", "error_description": "..."}`.

## 2. User approves

Direct the user to `verification_uri_complete` (or `verification_uri` + manually entering `user_code`). This is a normal authenticated, session-based HASP page — the user signs in if needed, sees the code, and approves or denies.

## 3. Poll the token endpoint

```
POST /v1/oauth/token
grant_type=urn:ietf:params:oauth:grant-type:device_code
```

```bash theme={null}
curl -X POST https://api.usehasp.com/v1/oauth/token \
  -d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
  -d "device_code=e6cf...redacted" \
  -d "client_id=hasp-cli"
```

Poll no more often than every `interval` seconds (the value returned in step 1). While the user hasn't approved yet, every poll returns:

```json theme={null}
{ "error": "authorization_pending", "error_description": "The user has not yet completed authorization." }
```

Once approved, the same poll returns a normal token response:

```json theme={null}
{
  "access_token": "hasp_pat_live_...",
  "token_type": "Bearer",
  "scope": "hasp.data.read hasp.data.write"
}
```

### Polling error codes (RFC 8628 §3.5)

| `error`                 | Meaning                                                       | What to do                                                 |
| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------------- |
| `authorization_pending` | Still waiting on the user.                                    | Keep polling at `interval`.                                |
| `slow_down`             | Polling too frequently.                                       | Increase your polling interval by 5 seconds and continue.  |
| `access_denied`         | The user denied the request.                                  | Stop polling. Restart the flow if the user wants to retry. |
| `expired_token`         | The device code's 15-minute window elapsed before approval.   | Stop polling. Request a new device code (step 1).          |
| `invalid_grant`         | Unknown device code, or it was already exchanged for a token. | Stop polling. A device code is single-use.                 |

## Security notes

* `user_code` is short and human-facing by design (RFC 8628's threat model accepts this given the 15-minute expiry and the fact that guessing it grants nothing without also completing an authenticated approval step).
* `device_code` is long, unguessable, and never displayed — only ever transmitted machine-to-machine between the CLI and the token endpoint.
* A device code is single-use: once exchanged for a token (or denied), polling it again returns `invalid_grant`.
